前言
很高兴遇见你~
在 深入剖析HashMap 文章中我从散列表的角度解析了HashMap,在 深入解析ConcurrentHashMap:感受并发编程智慧 解析了ConcurrentHashMap的底层实现原理。本文是HashMap系列文章的第三篇,主要内容是讲解与HashMap相关的集合类。
HashMap本身功能已经相对完善,但在某些特殊的情景下,他就显得无能为力,如高并发、需要记住key插入顺序、给key排序等。实现这些功能往往需要付出一定的代价,在没有必然的需求情景下,增添这些功能是没必要的。因而,为了提高性能,Java并没有把这些特性直接集成到HashMap中,拓展了拥有这些特性的其他集合类作为补充:
- 线程安全的ConcurrentHashMap、Hashtable、SynchronizeMap
- 记住插入顺序的LinkedHashMap
- 记录key顺序的TreeMap
这样,我们就可以在特定的需求情景下,选择最适合我们的集合框架,从而来提高性能。那么今天这篇文章,主要就是分析这些其他的集合类的特性、付出的性能代价、与HashMap的区别。
那么,我们开始吧~
Hashtable
Hashtable是属于JDK1.1的第一批集合框架其中之一,其他的还有Vector、Stack等。这些集合框架由于设计上的缺陷,导致了性能的瓶颈,在jdk1.2之后就被新的一套集合框架取代,也就是HashMap、ArrayList这些。HashMap在jdk1.8之后进行了全面的优化,而Hashtable依旧保持着旧版本的设计,在很多方面都落后于HashMap。下面主要分析Hashtable在:接口继承、哈希函数、哈希冲突、扩容方案、线程安全等方面解析他们的不同。
接口继承
Hashtable继承自Dictionary类而不是AbstractMap,类图如下(jdk1.8)
Hashtable诞生的时间是比Map早,但为了兼容新的集合在jdk1.2之后也继承了Map接口。Dictionary在目前已经完全被Map取代了,所以更加建议使用继承自AbstractMap的HashMap。为了兼容新版本接口还有Hashtable的迭代器:Enumerator。他的接口继承结构如下:
他不仅实现了旧版的Enumeration接口,同时也实现了Iteractor接口,兼容了新的api与使用习惯。这里关于Hashtable还有一个问题:Hashtable是fast-fail的吗 ?
fast-fail指的是在使用迭代器遍历集合过程中,如果集合发生了结构性改变,如添加数据、扩容、删除数据等,迭代器会抛出异常。Enumerator本身的实现是没有fast-fail设计的,但他继承了Iteractor接口之后,就有了fast-fail。看一下源码:
public T next() {
// 这里在Enumerator的基础上,增加了fast-fail
if (Hashtable.this.modCount != expectedModCount)
throw new ConcurrentModificationException();
// nextElement()是Enumeration的接口方法
return nextElement();
}
private void addEntry(int hash, K key, V value, int index) {
...
// 在添加数据之后,会改变modCount的值
modCount++;
}
所以,Hashtable本身的设计是有fastfail的,但如果使用的Enumerator,则享受不到这个设计了。
哈希算法
Hashtable的哈希算法非常简单粗暴,如下代码
hash =<