Java 为什么Hashmap在内部使用LinkedList而不是Arraylist

Java 为什么Hashmap在内部使用LinkedList而不是Arraylist,java,data-structures,hashmap,Java,Data Structures,Hashmap,当两个对象放在哈希表的同一个bucket中时,为什么Hashmap内部使用LinkedList而不是Arraylist?这基本上归结为Arraylist和LinkedList的复杂性。 在LinkedList中插入(当顺序不重要时)为O(1),只需附加到start。 ArrayList中的插入(当顺序不重要时)为O(N),遍历到末尾,并且还有调整大小的开销 删除LinkedList中的O(n),遍历和调整指针。 arraylist中的删除可以是O(n^2)、遍历到元素并移动元素或调整arrayl

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

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

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

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

当使用HashMap时,我们期望对add、remove和contains执行O(1)操作。使用ArrayList,我们将为桶中的添加、删除操作带来更高的成本

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

事实上,它两个都不使用(!)

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

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

正常的权衡是:

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

  • LinkedList
    使用了更多的空间,但是插入和删除所选元素是
    O(1)

然而,在通过将
HashMap
条目节点链接在一起形成的私有单链表的情况下,空间开销是一个引用(与
ArrayList
相同),插入节点的成本是
O(1)
(与
LinkedList
相同),并且移除所选节点的成本也是
O(1)
(与链接列表相同)

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


但它变得更加复杂。在Java 8中,他们彻底检查了
HashMap
内部数据结构。在当前的实现中,一旦哈希链超过某个长度阈值,如果密钥类型实现了
Comparable

简短回答,则实现将切换到使用二叉树表示:Java使用LinkedList或ArrayList(它认为适合数据的)

长答案

虽然排序的ArrayList看起来是显而易见的方法,但是使用LinkedList有一些实际的好处。 我们需要记住,LinkedList链仅在出现键冲突时使用。 但作为散列函数的一个定义:冲突应该很少

在很少发生冲突的情况下,我们必须在排序的ArrayList或LinkedList之间进行选择。 如果我们比较排序的ArrayList和LinkedList,有一些明显的权衡

  • 插入和删除:排序的ArrayList接受O(n),但LinkedList接受常数O(1)
  • 检索:排序后的ArrayList取O(logn),LinkedList取0(n)
  • 现在,很明显,LinkedList在插入和删除时比排序的ArrayList好,但在检索时却不好

    在冲突较少的情况下,排序后的ArrayList带来的价值较小(但更大)。
    但当碰撞更频繁且碰撞的元素列表变大时(超过某个阈值)Java将冲突数据结构从LinkedList更改为ArrayList。

    考虑删除的成本。从AL中删除特定项在时间和空间上都比从LL中删除更复杂。除此之外,ALs的好处根本没有被使用,就像通过索引查找项一样。您还有两个问题吗?请注意,它使用的是链接的l列表,而不是java.util.LinkedList。这些条目只是有一个指向bucket中下一个元素的指针。此列表通常只包含一个元素,或者至少包含很少的元素,因此遍历成本不是问题。而且按索引访问没有用,因此不需要数组。使用ArrayList会消耗太多内存(如果数组太大),或者需要频繁复制到较大的数组(如果数组太小)。链表OTOH所使用的内存与元素数成正比,添加新条目总是O(1)。“arraylist中的删除可能是O(n^2),遍历到元素并移动元素”:这实际上只使其为O(n),因为O(n+n)=O(2*n)=O(n)。还要考虑内存开销。
    ArrayList
    overallocates,对于低负载因子的大型哈希表,这一点变得非常重要。“插入ArrayList(顺序不重要时)是O(n),遍历到末尾,还有调整大小的开销。”:这似乎是一个误解。在基于数组的列表中,您通常知道结尾在哪里,因此插入(包括摊销大小调整)也在那里。Java Hashmap使用单个链接列表,而不使用Arraylist。请参考上述说明。