上一篇中我们共同探讨了HashMap,并且也提到了Hashtable与HashMap很相似,除了Hashtable是同步的以及不允许null值外。现在,趁热打铁,开始Hashtable的学习。
简介
老规矩,先过一遍注释。
-
任何非null的对象都可以作为key或者value存入其中。
-
存入其中的key必须实现了hashCode以及equals方法。
-
与HashMap一样,有两个参数影响Hashtable的性能,初始容量initial capacity以及负载因子load factor。他们的作用于HashMap中的相同,详细请看这篇文章
-
如果Hashtable中要存放很多键值对,那么在一开始把他的初始容量设置的大点比较好。
-
Hashtable返回的迭代器即Iterator都是基于快速失败机制fail-fast的。
-
不像其他新的集合实现,Hashtable是同步的,所以在不需要考虑线程安全的情况下,推荐使用HashMap替代Hashtable以获得更好的性能。如果需要一个高度线程安全的同步,则推荐使用ConcurrentHashMap代替Hashtable。(感觉都可以把Hashtable从Jdk中删掉了,奶奶不疼姥姥不爱的:joy:)。
存储结构
先来看一下它的属性:
其实这些一看就知道了,但是我们还是一个个的说说。
- table: 这就是存储键值对的地方了,一会我们图解一下。
- count: 存储的键值对的总数
- threshold: 当Hashtable中键值对的数量超过这个值后就会进行扩容操作,这个值等于容量与负载因子的乘积
- loadFactor: 负载因子,代表了这个Hashtable可允许的满度。这个容器可以存储最多可以存储此容器容量的百分之多少。
- modCount: 快速失败机制用到,不做赘述。
其实Hashtable的存储结构也是数组+链表的形式,我们图解一下:
可以看到其实是跟HashMap未树化前的存储结构相同。
结合HashMap进行对比
先从构造函数看起
在上篇文章中,我们说到,其实在HashMap中并没有直接使用我们传入的参数initialCapacity,而是使用一个方法将其转换成离其最近的2的n次方数字,但是Hashtable并不是如此,在代码中可以看到,在构造函数中直接使用initialCapacity对table进行了初始化。并且随机计算出了阈值threshold。
我们多次提到Hashtable是同步的,那这个同步体现在哪里呢?就体现在下面:
看出来了吗?其实就体现在这些方法前面加的关键字synchronized。
扩容机制
我们直接来看扩容机制,不去看那些大同小异的put方法了。我们看看rehash的源码:
同样的源码中,先去根据旧的容量求出新的容量也就是table数组的大小。我们注意到新的容量不是直接是旧的容量的二倍而是旧的容量的二倍加一
其次,我们可以注意到,Hashtable用了一个for循环对其中的每个元素根据其Hash重新计算了其在新数组中的索引。
总结
-
不需要考虑线程安全时用HashMap提高性能,需要考虑线程安全时用ConcurrentHashMap
-
同步体现在对每个方法使用了synchronized关键字。
-
存储结构是数组+链表
-
扩容时默认扩容为原来的两倍加一
-
扩容时在进行元素复制时对旧数组中存储的每个元素重新计算了索引并插入到新数组中。
转载于:https://juejin.im/post/5cf26334e51d455070226f3d