为啥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
在内部使用sLinkedList
而不是Arraylist
,当两个对象被放入哈希表的同一个桶中时?
实际上,它都不使用任何一个 (!)。
它实际上使用了一个通过链接哈希表条目实现的单链表。 (相比之下,LinkedList
是双向链接的,它要求列表中的每个元素都有一个单独的 Node
对象。)
那我为什么要在这里吹毛求疵?因为它实际上很重要……因为这意味着LinkedList
和ArrayList
之间的正常 权衡不适用。
正常的权衡是:
ArrayList
使用较少的空间,但在最坏的情况下插入和删除选定元素是O(N)
。
LinkedList
使用更多空间,但插入和删除选定元素1 是O(1)
。
但是,在将HashMap
入口节点链接在一起形成的私有单链表的情况下,空间开销是一个引用(同ArrayList
),插入一个节点的成本是O(1)
(同为LinkedList
),移除一个选中的节点的成本也是O(1)
(与LinkedList
相同)。
仅依靠“大 O”进行此分析是可疑的,但是当您查看实际代码时,很明显 HashMap
在删除和插入性能上确实击败了 ArrayList
,并且在查找方面具有可比性. (这忽略了内存局部性影响。)而且它还使用比 ArrayList
或 LinkedList
更少的内存用于链接......考虑到已经有内部条目对象来保存键/值对。
但它变得更加复杂。在 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四种结构各自的特点。
为啥Java中的BitSet使用long数组做内部存储,而不使用int数组...