一篇文章带你搞定HashTable
在咱们开讲源码之前,首先需要了解下什么是哈希表?
散列表(Hash table 又称哈希表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中的一个位置来访问记录,以加快查找的速度.这个映射函数就叫做散列函数,存放记录的数组叫做散列表. —— 百度百科
如图:
在Java中HashTable以数组
+链表
来实现,相对于HashMap来说要简单得多.HashTable不同于HashMap,它内部不允许插入null
值,同时它是线程安全的,所有的读写操作都进行了锁保护,但也难以避免的对读写效率产生了较大影响.因此在日常开发中为保证线程安全一般建议使用ConcurrentHashMap
.
为啥Java中
Hashtable
中的t
要小写? 这不符合驼峰命名规则啊
大意: Hashtable
创建于Java1,而集合的统一命名规范是后来在Java2中建立的,而当时又发布了新集合来代替它,再加上大量Java程序使用Hashtable
类,考虑到兼容问题不可能将Hashtable
改为HashTable
.同时Hashtable
已经过时了,不建议在代码中使用.
源码分析
结构图
继承关系
public class Hashtable<K,V>extends Dictionary<K,V>implements Map<K,V>, Cloneable, java.io.Serializable {
Dictionary
是JDK1.0里引入的抽象类,用于存储键/值对,作用和Map类似.注意Dictionary
类已经过时了,在实际开发中,可以通过实现Map
接口来完成存储键值对的功能.
类中属性
/** 内部维护了一个 Entry 数组 */private transient Entry<?,?>[] table;/** 哈希表里的元素数量 */private transient int count;/** 触发扩容的阈值 */private int threshold;/** 加载因子 默认 0.75 */private float loadFactor;/** 记录 涉及到结构变化的次数(offer/remove/clear等) */private transient int modCount = 0;/** 版本号 */private static final long serialVersionUID = 1421746759512286392L;
table
数组里存的Entry
实际上是一个单向链表,哈希表的键值对都是存在table
里的.
private static class Entry<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Entry<K,V> next;...}
构造函数
public Hashtable() { this(11, 0.75f); }public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); }public Hashtable(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal Load: "+loadFactor);//初始容量最小为1if (initialCapacity==0) initialCapacity = 1;this.loadFactor = loadFactor;table = new Entry<?,?>[initialCapacity];threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);}
Hashtable的默认容量是11
,loadFactor
默认加载因子0.75
.threshold
为
数组容量 * loadFactor
.
核心函数
添加函数
public synchronized V put(K key, V value) {if (value == null) {throw new NullPointerException();}Entry<?,?> tab[] = table;int hash = key.hashCode();// &运算取正值,再取模计算位置int index = (hash & 0x7FFFFFFF) % tab.length;Entry<K,V> entry = (Entry<K,V>)tab[index];// 如果这个hash和key都已经存在了,就把原来的value替换掉for(; entry != null ; entry = entry.next) {if ((entry.hash == hash) && entry.key.equals(key)) {V old = entry.value;entry.value = value;return old;}}// 真正的添加操作addEntry(hash, key, value, index);return null;
}
put
函数通过关键字synchronized
保证了线程安全.第一行就表明了Hashtable中value都不能为null
.通过hash & 0x7FFFFFFF
来规避掉负数,再进行分配位置.如果key经常存在了,则覆盖旧值并返回旧值.核心通过addEntry
来添加元素.
private void addEntry(int hash, K key, V value, int index) {modCount++;Entry<?,?> tab[] = table;// 若当前容量已经大于 阈值 进行rehash扩容if (count >= threshold) { // threshold = count * loadFlor// Rehash the table if the threshold is exceededrehash();tab = table;hash = key.hashCode();index = (hash & 0x7FFFFFFF) % tab.length;}// Creates the new entry.Entry<K,V> e = (Entry<K,V>) tab[index];// 最新插入的 排在最前面tab[index] = new Entry<>(hash, key, value, e);count++;}
addEntry
函数 先会判断如果需要扩容 当前数量 >= 阈值
,调用rehash
进行扩容,否则链表叠加.
protected void rehash() {int oldCapacity = table.length;Entry<?,?>[] oldMap = table;// 新容量为老容量的一倍+1,int newCapacity = (oldCapacity << 1) + 1;if (newCapacity - MAX_ARRAY_SIZE > 0) {if (oldCapacity == MAX_ARRAY_SIZE)// Keep running with MAX_ARRAY_SIZE bucketsreturn;newCapacity = MAX_ARRAY_SIZE;}Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];modCount++;// 新阈值threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);table = newMap;for (int i = oldCapacity ; i-- > 0 ;) {for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {Entry<K,V> e = old;old = old.next;int index = (e.hash & 0x7FFFFFFF) % newCapacity;e.next = (Entry<K,V>)newMap[index];newMap[index] = e;}}}
rehash
函数先会把容量扩大1倍+1,然后创建一个newMap
,把oldMap
里的元素遍历复制到新的newMap
里,这个过程是比较耗时的,同时此操作后数组和链表里元素的位置都会发生改变.
删除函数
public synchronized V remove(Object key) {Entry<?,?> tab[] = table;int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length;Entry<K,V> e = (Entry<K,V>)tab[index];// 遍历链表 e 当前节点, prev 上一个节点 next 下一个节点for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {// 性能优化 先 进行hash 判断if ((e.hash == hash) && e.key.equals(key)) {modCount++;if (prev != null) {// 若当前节点非头结点prev.next = e.next;} else {tab[index] = e.next;}count--;V oldValue = e.value;e.value = null;return oldValue;}}return null;}
remove
函数比较简单,通过key
定位在数组中的位置,再遍历链表删除元素.
获取函数
public synchronized V get(Object key) {Entry<?,?> tab[] = table;int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length;for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {if ((e.hash == hash) && e.key.equals(key)) {return (V)e.value;}}return null;}
get
函数和remove
函数比较类似,都是先通过key
定位在数组中的位置,再迭代链表找到元素并直接返回.
迭代器
Hashtable
里迭代器使用的是比较老的Enumeration
接口,其作用和Iterator
类似,只提供了遍历的功能.虽然Enumeration
还未被废弃,但现在代码里已经很少使用了.本篇文章就不再讲述了,有兴趣的可以去翻翻代码~
public interface Enumeration<E> {/** 判断是否还有元素 */boolean hasMoreElements();/** 如果还有元素则返回下一个元素,否则抛NoSuchElementException异常 */E nextElement();
}
结语
Hashtable
整体是比较简单的,其内部充斥着大量的遍历操作,当数据量大的时候操作会非常耗时,非特殊情况下是用不到的.
一般来说,在日常开发中非并发场景推荐使用HashMap
,并发场景下虽然可以用Hashtable
,但是更推荐使用ConcurrentHashMap
.
ConcurrentHashMap
内部虽然也是使用Synchronized
,但它是针对单个对象的锁,相比于Hashtable
里锁的粒度更细,效率更高.
一篇文章带你搞定HashTable相关推荐
- java 不重启部署_一篇文章带你搞定SpringBoot不重启项目实现修改静态资源
一.通过配置文件控制静态资源的热部署 在配置文件 application.properties 中添加: #表示从这个默认不触发重启的目录中除去static目录 spring.devtools.res ...
- 一篇文章带你搞定 MongoDB 实现 REST
文章目录 一.前期准备 二.SpringBoot 实现 MongoDB 的 REST 三.使用 Postman 测试使用 一.前期准备 首先使用 docker 搭建好 MongoDB:一篇文章带你搞定 ...
- 一篇文章带你搞定19年数学建模机场出租车优化问题示例讲解含代码
文章目录 一.问题分析 二.数据介绍 三.模型的求解 四.结果分析 一.问题分析 收集国内某一机场及其所在城市出租车的相关数据,给出该机场出租车司机的选择方案,并分析模型的合理性和对相关因素的依赖性. ...
- 如何使用RSA 对数据加解密和签名验签?一篇文章带你搞定
点击上方"Python爬虫与数据挖掘",进行关注 回复"书籍"即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 三分割据纡筹策,万古云霄一羽毛. ...
- 一篇文章带你搞定二维插值的 MATLAB 计算
前面已经学习了二维插值的基本概念:一篇文章带你认识数学建模中的二维插值 本篇文章主要实现使用MATLAB进行二维插值计算 文章目录 一.网格节点的插值计算 二.散点数据的插值计算 1. 示例 1 2. ...
- 一篇文章带你搞定 SpringBoot 自定义欢迎页和网页图标 favicon
文章目录 一.SpringBoot 自定义项目启动欢迎页 二.SpringBoot 自定义 favicon 一.SpringBoot 自定义项目启动欢迎页 已经分析过SpringBoot 的资源访问路 ...
- 一篇文章带你搞定 Ubuntu 中打开 Pycharm 总是卡顿崩溃
由于 Ubuntu 中的汉字输入实在是太不友好了,所以装了个 搜狗输入法,好不容易把 搜狗输入法装好,本以为可以开开心心的搞代码了,然而... pycharm 一打开,就崩溃,关不掉,进程杀死还是不行 ...
- 一篇文章带你搞定 Java 日志框架 slf4j
文章目录 一.门面模式 二.为什么要使用 slf4j ? 三.如何使用 一.门面模式 slf4j是门面模式的典型应用,因此在讲slf4j前,先简单学习下门面模式, 门面模式,其核心为外部与一个子系统的 ...
- 一篇文章带你搞定 HTTPS
文章目录 一.HTTP加上加密处理和认证以及完整性保护后即是HTTPS 二.HTTPS是身披SSL外壳的HTTP 三.相互交换密钥的公开密钥加密技术 1. 共享密钥加密的困境 2. 使用两把密钥的公开 ...
最新文章
- Squid access.log 转发到其他syslog服务器(OSSIM)
- phpStorm 2016.1 最新版激活方法
- 禁止snmpd往syslog中写入无用信息
- Windows 下各种Python库的下载与安装
- python中变量类型在程序中可以改变_详细解析Python当中的数据类型和变量
- 1 java基础增强
- 6个座位办公室最佳位置_办公室座位最佳位置(讲解)
- ps去水印教程_叫板 PS!去水印、抠图、加滤镜,这款超强修图应用到底什么来头...
- RS232 9针串口定义
- 跑PIN码破解无线网络WIFI密码的原理分析(转)
- 《ERP高级计划》书解读-APS案例分析之四缓冲的计算(蔡颖)(转)
- P8198 [传智杯 #4 决赛] 背单词的小智 二分答案+前缀和
- 第十一课:磁场和洛伦兹力
- AD936x 系列快速入口
- 威洛特:狗狗乱咬东西都是有原因的
- 网络营销技巧:有必要为移动端专门做站吗?
- SLsec招新题wp
- dht11温湿度传感器特点及使用介绍
- python求解多元多次方程组或非线性方程组
- 关于AnyChat录像解决方案的几张原理图
热门文章
- Access-Control-Expose-Headers
- linux Ubuntu网卡配置,Windows 7下用VirtualBox安装Ubuntu网卡配置
- 转行做程序员之前你应该考虑的三件事
- jenkins Error performing command: git ls-remote -h
- 微信小程序仿系统自带下拉刷新效果
- 或非门sr锁存器_SR锁存器为什么可以实现存储的功能?
- 2小时07分30秒!尘封逾15年中国马拉松国家纪录被打破 | 美通社头条
- Fegin x-www-form-urlencoded 类型请求
- 为新机让路走降价路线的两部手机,市场竞争太激烈,旗舰机也扛不住
- 生成HTML报告时,报错:AttributeError: type object ‘_io.StringIO‘ has no attribute ‘StringIO‘