我对java.util.HashMap的概念性理解如下:
与其他Map实现相比,它的主要资产是常量查找时间,假设没有冲突。出于这个原因,底层实现使用固定长度的数组 - 计算机科学中唯一具有O(1)查找的数据结构。
用于存储Map条目的固定长度数组在实例化时初始化为给定大小,并且(通过扩展,我的意思是创建一个更大的数组并将值复制)作为Map的大小接近固定长度数组的长度。
将值放入Map时,键值对将放入给定键的内部链接列表实现中。当发生冲突时,后续键值对将附加到列表中。
从Map获取时,密钥的hashCode()用于派生内部链表实现的数组索引,如果列表的大小为1,则要么拥有值,要么迭代遍历列表在每个元素的键上调用equals(),直到找到您的值。
基于第2点,HashMap必须扩展一个数组,这个操作肯定是线性的。为什么它使用内部链表实现(O(n)查找)来进行冲突解决?为什么不使用带有O(log n)查找的数据结构(如二进制或红黑树)来提高性能?
答案 0 :(得分:3)
http://openjdk.java.net/jeps/180
从Java 8开始,如果有足够的冲突,HashMap会回退到二叉树。
答案 1 :(得分:2)
虽然它不能保证O(1)插入时间,但它确实具有摊销 O(1)插入时间,也就是说如果你逐个插入大量元素,插入它们所需的总时间将与您插入的元素数成正比。
改变用于存储桶的数据结构不会改善这种情况。阵列扩展的要点是确保每个桶中预期的条目数常量;这意味着即使使用链接列表,仍然可以进行常量插入和查找。
在扩展时间以及扩展数量(数组大小加倍)方面,数字都经过精心设计。它与ArrayList
中使用的技术非常相似,以保证向列表中添加摊销的O(1)。