在对三者进行比较之前,需要先掌握三者的实现原理
1.实现原理
1.1 Hashmap
Hashmap的底层实现原理要分成jdk1.8之前和jdk1.8之后两种情况叙述。
(1)在jdk1.8以前,Hashmap底层是数组和链表结合在一起使用,也就是链表散列。
(2)在jdk1.8以后,Hashmap底层是数组和链表\红黑树。
在jdk1.8以后解决哈希冲突的方法变了,jdk1.8以前的存储方式可能出现的一种问题是,如果发生冲突的键很多,假设最差情况下,所有的键都通过一个链表存储在数组的同一个元素中,那么此时查找所需要的时间复杂度就是O(n),所以在jdk1.8以后,如果链表长度大于8且节点数组长度大于64的时候
,就把链表下所有的节点转为红黑树。红黑树的查找时间复杂度一直是O(logn),所以也就解决了上面这种问题。
(3)Hashmap的扩容原理:
当HashMap决定扩容时,会调用HashMap类中的resize(int newCapacity)方法,参数是新的table长度。在JDK1.7和JDK1.8的扩容机制有很大不同。
JDK1.7下的resize()方法是这样的:
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
代码中可以看到,如果原有table长度已经达到了上限,就不再扩容了。
如果还未达到上限,则用新容量创建一个新的table,并调用transfer方法把原table的Node放到新的table中:
/**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next; //注释1
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity); //注释2
e.next = newTable[i]; //注释3
newTable[i] = e; //注释4
e = next; //注释5
}
}
}
transfer方法的作用是把原table的Node放到新的table中,使用的是头插法,也就是说,新table中链表的顺序和旧列表中是相反的,在HashMap线程不安全的情况下,这种头插法可能会导致环状节点。
参考文献:
https://blog.csdn.net/lkforce/article/details/89521318 Hashmap实现原理及扩容机制详解