为什么java.util.HashMap在内部使用链表

为什么java.util.HashMap在内部使用链表,java,hashmap,Java,Hashmap,我对java.util.HashMap的概念性理解如下: 与其他映射实现相比,它的主要优点是恒定的查找时间,前提是不存在冲突。由于这个原因,底层实现使用固定长度的数组——计算机科学中唯一具有O(1)查找的数据结构 用于存储映射项的固定长度数组在实例化时初始化为给定大小,并在映射的大小接近固定长度数组的长度时展开(通过展开,我的意思是创建一个更大的数组并复制值) 当一个值被放入映射中时,键值对被放入给定键的内部链表实现中。当发生冲突时,随后的键值对将追加到列表中 从映射中获取时,键的hashCod

我对java.util.HashMap的概念性理解如下:

  • 与其他映射实现相比,它的主要优点是恒定的查找时间,前提是不存在冲突。由于这个原因,底层实现使用固定长度的数组——计算机科学中唯一具有O(1)查找的数据结构

  • 用于存储映射项的固定长度数组在实例化时初始化为给定大小,并在映射的大小接近固定长度数组的长度时展开(通过展开,我的意思是创建一个更大的数组并复制值)

  • 当一个值被放入映射中时,键值对被放入给定键的内部链表实现中。当发生冲突时,随后的键值对将追加到列表中

  • 从映射中获取时,键的hashCode()用于派生内部链表实现的数组索引,如果列表的大小为1,则您可以获得值,或者在每个元素的键上遍历调用equals()的列表,直到找到值为止


  • 基于第2点,HashMap必须扩展一个数组,这个操作肯定是线性的。为什么它使用内部链表实现(O(n)查找)来解决冲突?为什么它不使用具有O(logn)查找的数据结构(如二进制或红黑树)来提高性能?

    虽然它不能保证O(1)插入时间,但它确实分摊了O(1)插入时间,也就是说,如果您逐个插入大量元素,插入它们所花费的总时间将与插入的元素数量成比例

    改变用于存储桶的数据结构不会改善这一点。数组扩展的目的是确保每个bucket中预期的条目数保持不变;这意味着即使使用链表,仍然可以进行固定时间的插入和查找


    这些数字是非常仔细地计算出来的,包括何时扩展以及扩展多少(将数组的大小增加一倍)。这是一种与ArrayList中使用的技术非常相似的技术,以保证向列表中添加摊销O(1)。

    虽然它不保证O(1)插入时间,但它确实有摊销O(1)插入时间,也就是说,如果您逐个插入大量元素,插入它们所花费的总时间将与插入的元素数量成比例

    改变用于存储桶的数据结构不会改善这一点。数组扩展的目的是确保每个bucket中预期的条目数保持不变;这意味着即使使用链表,仍然可以进行固定时间的插入和查找

    这些数字是非常仔细地计算出来的,包括何时扩展以及扩展多少(将数组的大小增加一倍)。这是一种与ArrayList中使用的技术非常相似的技术,以保证将摊销O(1)添加到列表中。

    从Java8开始,如果有足够多的冲突,HashMap会返回到二叉树。


    从Java 8开始,如果有足够多的冲突,HashMap确实会返回到二叉树。

    因为人们希望每个bucket最多只包含少量条目。因为您希望首先只得到很少的冲突。如果我没记错的话,如果一个bucket中的冲突数超过某个阈值,Java 8确实会回到二进制搜索树。@LouisWasserman,谢谢你的输入,真是有趣的东西!有没有可能链接到一些文档或源代码?这将是伟大的正式答复!因为人们期望每个bucket最多只包含几个条目,因为你首先只希望得到很少的冲突。如果我没记错的话,如果一个bucket中的冲突数超过某个阈值,Java 8确实会回到二进制搜索树。@LouisWasserman,谢谢你的输入,真是有趣的东西!有没有可能链接到一些文档或源代码?这将是伟大的正式答复!我不是建议改进插入时间。我建议改进bucket中存在冲突的条目的查找时间。在链表中搜索一个条目是O(n/2),但也有一些数据结构,例如使用O(logn)查找的一些树实现。我认为上面的评论已经说明了这一点——你只是不希望有足够的碰撞来让它变得有价值。@RobertBain你说基于第2点,HashMap不能保证插入时间。扩展阵列肯定是线性的。第一段是对这个问题的回答:它确实有摊销插入时间。其余部分回答了您的主要问题:bucket中预期的条目数是恒定的,因此查找仍然是恒定的。我正确地回答了你的主要问题,也处理了你帖子中的一个误解。首先感谢你的回答,我感谢你的时间,但我仍然觉得我对我的理解不太满意。你能解释一下“数组扩展的目的是确保每个bucket中预期的条目数是恒定的”这句话吗?在我看来,数组扩展的意义在于,键的单独散列值比散列映射中的值要多。我的理解是,根据hashCode()值为键值对分配一个数组索引。如果我遗漏了什么,我会道歉。@RobertBain你在这两方面都是对的。扩展数组的目的是为条目留出更多的空间,并以适当的速率进行扩展,以确保如果哈希函数有效地返回随机值,则平均而言,数组中会有少量(恒定)的条目