为啥Hashmap内部使用LinkedList而不是Arraylist

Posted

技术标签:

【中文标题】为啥Hashmap内部使用LinkedList而不是Arraylist【英文标题】:Why does Hashmap Internally use LinkedList instead of Arraylist为什么Hashmap内部使用LinkedList而不是Arraylist 【发布时间】:2015-09-06 14:11:13 【问题描述】:

为什么Hashmap 在内部使用LinkedList 而不是Arraylist 当两个对象放在哈希表的同一个桶中?

【问题讨论】:

考虑移除成本。 从 AL 中删除特定项目在时间和空间上都比从 LL 中更复杂。除此之外,根本没有使用 AL 的好处,例如通过索引查找项目 你还有两个问题是什么? 请注意,它使用的是链表,而不是 java.util.LinkedList。这些条目只是有一个指向桶中下一个条目的指针。这个列表一般应该只包含一个元素,或者至少很少,所以遍历成本不是问题。而且按索引访问也没用,所以不需要数组。 使用 ArrayList 会消耗太多内存(如果数组太大),或者需要频繁复制到更大的数组(如果数组太小)。链表OTOH使用的内存与元素的数量成正比,添加一个新条目总是O(1)。 【参考方案1】:

为什么HashMap在内部使用s LinkedList而不是Arraylist,当两个对象被放入哈希表的同一个桶中时?

实际上,它都不使用任何一个 (!)。

它实际上使用了一个通过链接哈希表条目实现的单链表。 (相比之下,LinkedList 是双向链接的,它要求列表中的每个元素都有一个单独的 Node 对象。)

那我为什么要在这里吹毛求疵?因为它实际上很重要……因为这意味着LinkedListArrayList 之间的正常 权衡不适用。

正常的权衡是:

ArrayList 使用较少的空间,但在最坏的情况下插入和删除选定元素是O(N)

LinkedList 使用更多空间,但插入和删除选定元素1O(1)

但是,在将HashMap入口节点链接在一起形成的私有单链表的情况下,空间开销是一个引用(同ArrayList),插入一个节点的成本是O(1)(同为LinkedList),移除一个选中的节点的成本也是O(1)(与LinkedList相同)。

仅依靠“大 O”进行此分析是可疑的,但是当您查看实际代码时,很明显 HashMap 在删除和插入性能上确实击败了 ArrayList,并且在查找方面具有可比性. (这忽略了内存局部性影响。)而且它还使用比 ArrayListLinkedList 更少的内存用于链接......考虑到已经有内部条目对象来保存键/值对。

但它变得更加复杂。在 Java 8 中,他们彻底检查了 HashMap 内部数据结构。在当前的实现中,一旦哈希链超过某个长度阈值,如果键类型实现Comparable,则实现切换到使用二叉树表示。


1 - 如果您找到插入/删除点,则插入/删除是O(1)。例如,如果您在 LinkedList 对象的 ListIterator 上使用插入和删除方法。

【讨论】:

【参考方案2】:

简短回答:Java 使用 LinkedList 或 ArrayList(无论哪个它认为适合数据)。

长答案

虽然 sorted ArrayList 看起来很明显,但使用 LinkedList 有一些实际好处。 我们需要记住,LinkedList 链仅在发生键冲突时使用。 但是作为哈希函数的定义:碰撞应该是罕见的

在极少数的冲突情况下,我们必须在 Sorted ArrayList 或 LinkedList 之间进行选择。 如果我们比较 sorted ArrayList 和 LinkedList 会有一些明显的权衡

    插入和删除:Sorted ArrayList 需要 O(n),但 LinkedList 需要常数 O(1) 检索:Sorted ArrayList 需要 O(logn),LinkedList 需要 0(n)。

现在,很明显 LinkedList 在插入和删除时优于 sorted ArrayList,但在检索时却很糟糕。

在更少的冲突中,排序的 ArrayList 带来的价值更少(但更多的开销)。 但是当碰撞更加频繁并且碰撞元素列表变大(超过一定阈值)时,Java 将碰撞数据结构从 LinkedList 更改为 ArrayList。

【讨论】:

Java Hashmap 使用单链表,不使用 Arraylist。请参考上述说明。【参考方案3】:

这基本上归结为 ArrayList 和 LinkedList 的复杂性。 LinkedList 中的插入(当顺序不重要时)是 O(1),只需附加到开始。 在 ArrayList 中插入(当顺序不重要时)是 O(N) ,遍历到 end 并且还有调整大小的开销。

LinkedList中的移除是O(n),遍历并调整指针。 arraylist 中的删除可能是 O(n^2) ,遍历元素并移动元素或调整 Arraylist 的大小。

在任何一种情况下,包含将是 O(n)。

当使用 HashMap 时,我们将期望 O(1) 的添加、删除和包含操作。使用 ArrayList 会导致桶中添加、删除操作的成本更高

【讨论】:

"arraylist 中的删除可能是 O(n^2) ,遍历元素并移动元素":这实际上只会使其 O(n) i>,因为 O(n + n) = O(2*n) = O(n). 还要考虑内存开销。 ArrayList 过度分配,对于具有低负载因子的大型哈希表,这变得非常重要。 “ArrayList 中的插入(当顺序不重要时)是 O(N) ,遍历到结束并且还有调整大小的开销。”:这似乎是一个误解。在基于数组的列表中,您通常知道结尾在哪里,因此插入(包括摊销调整大小)也是 O(1)

以上是关于为啥Hashmap内部使用LinkedList而不是Arraylist的主要内容,如果未能解决你的问题,请参考以下文章

结合数据结构所学知识,简要说明Java语言中ArrayList,LinkedList,HashSet,HashMap四种结构各自的特点。

LinkedList与ArrayList的区别(内部实现)

Java-数据结构

为啥Java中的BitSet使用long数组做内部存储,而不使用int数组...

何时在 LinkedList 或 ArrayList 上使用 HashMap,反之亦然

JDK源码分析——HashMap 上(基于JDK7)