【问题标题】:How is the internal implementation of LinkedHashMap different from HashMap implementation?LinkedHashMap 的内部实现与 HashMap 实现有何不同?
【发布时间】:2013-11-13 08:59:51
【问题描述】:

我读到 HashMap 有以下实现:

main array 
   ↓
[Entry] → Entry → Entry      ← linked-list implementation
[Entry]
[Entry] → Entry
[Entry]
[null ]

所以,它有一个 Entry 对象数组。

问题:

  1. 我想知道这个数组的索引如何在 hashCode 相同但对象不同的情况下存储多个 Entry 对象。

  2. 这与LinkedHashMap 的实现有何不同?它是 map 的双向链表实现,但它是否像上面一样维护一个数组,它如何存储指向下一个和前一个元素的指针?

【问题讨论】:

  • 只是一个猜测,但在每个 key->value 对内部,该值存储指向其他条目的下一个和上一个指针以保持插入顺序,这保留了顺序并仍然允许恒定时间删除插入和检索
  • 好的。我只是这么想。那么,数组中的每个Entry 对象都有指向下一个和上一个Entry 对象的链接?
  • 我正在添加一个答案以更好地解释它

标签: java hashmap linkedhashmap


【解决方案1】:

所以,它有一个 Entry 对象数组。

不完全是。它有一个Entry 对象 数组。 HashMap.Entry 对象有一个 next 字段,允许将 Entry 对象链接为链表。

我想知道这个数组的索引如何存储多个 Entry 对象,以防 hashCode 相同但对象不同。

因为(如您问题中的图片所示)Entry 对象是链式的。

这与LinkedHashMap 的实现有何不同?它是 map 的双向链表实现,但它是否像上面一样维护一个数组,它如何存储指向下一个和前一个元素的指针?

LinkedHashMap 实现中,LinkedHashMap.Entry 类通过添加beforeafter 字段扩展了HashMap.Entry 类。这些字段用于将LinkedHashMap.Entry 对象组装成一个记录插入顺序的独立双向链表。因此,在LinkedHashMap 类中,每个条目对象都位于两个不同的链中:

  • 有许多通过主哈希数组访问的单链接哈希链。这用于(常规)hashmap 查找。

  • 有一个单独的双向链表包含所有条目对象。它以条目插入顺序保存,并在您迭代哈希图中的条目、键或值时使用。

【讨论】:

  • 感谢您的所有回答!阅读所有这些确实让我有点困惑。但我觉得这个更清楚一点!因此接受这个作为答案!
【解决方案2】:

HashMap 不维护插入顺序,因此它不维护任何双向链表。

LinkedHashMap 最显着的特点是它维护了键值对的插入顺序。 LinkedHashMap 使用双向链表来做到这一点。

LinkedHashMap 的入口是这样的-

  static class Entry<K, V> {
     K key;
     V value;
     Entry<K,V> next;
     Entry<K,V> before, after;        //For maintaining insertion order    
     public Entry(K key, V value, Entry<K,V> next){
         this.key = key;
         this.value = value;
         this.next = next;
     }
  }

通过使用 before 和 after - 我们跟踪 LinkedHashMap 中新添加的条目,这有助于我们维护插入顺序。

Before 指上一个条目,并且 after 指的是 LinkedHashMap 中的下一个条目。

图表及分步说明请参考http://www.javamadesoeasy.com/2015/02/linkedhashmap-custom-implementation.html

谢谢..!!

【讨论】:

  • 图表很方便,也更详尽。
  • 嗨,我只是想知道上图中的 next 和 after 有什么区别
  • @GurinderSPanesar 如果您仔细观察上图。 'next' 是指向同一桶列表中的下一个条目的指针(更具体地说,具有相同的哈希码)'after' 是根据插入顺序指向下一个条目的指针。
  • 图表和参考链接真的很有帮助。
  • 因此证明。一张图片胜过千言万语。感谢分享。
【解决方案3】:

给自己一个look。为了将来参考,你可以谷歌:

java LinkedHashMap源码

HashMap使用LinkedList来处理碰撞,但HashMapLinkedHashMap的区别在于LinkedHashMap有一个可预测的迭代顺序,这是通过一个额外的双向链表实现的,通常维护键的插入顺序。例外情况是重新插入键时,在这种情况下,它会回到列表中的原始位置。

作为参考,遍历LinkedHashMap 比遍历HashMap 更有效,但LinkedHashMap 的内存效率较低。

如果我上面的解释不清楚,散列过程是相同的,所以你得到了普通散列的好处,但你也得到了如上所述的迭代好处,因为你使用的是双重链表来维护您的 Entry 对象的顺序,它独立于在哈希冲突期间使用的链表,以防不明确..

编辑:(回应 OP 的评论):
HashMap 由一个数组支持,其中一些插槽包含 Entry 对象链来处理冲突。要遍历所有 (key,value) 对,您需要遍历数组中的所有插槽,然后遍历 LinkedLists;因此,您的总时间将与容量成正比。

使用LinkedHashMap时,只需遍历双向链表,总时间与大小成正比。

【讨论】:

  • 有人可以解释一下否决票吗?我在这里说的没有什么是不正确的。
  • 我也有一个,我敢打赌是刚才回答的那个人
  • 我没有否决这个答案。为了记录,我决定自己写,因为我认为你的问题没有直接解决 OP 的问题,也没有真正清楚地解释实现细节。
  • @aaronman 这不是关于代表,而是关于提供可能的最佳答案。他的回答看起来不错,而且比我的更清楚。没有以任何方式解释随机投票,但是,这又不是他做的......
  • @aaronman - 你可以这样做。但这不是“完成”的方式。如果您“干涉”他们的回答,人们可能会感到被冒犯。 (我很少这样做......当我看到一个被接受的答案是危险的错误时。)
【解决方案4】:

由于其他答案都没有真正解释如何实现这样的事情,我会试一试。

一种方法是在(键->值对的)值中包含一些对用户不可见的额外信息,这些信息对插入到哈希映射中的前一个和下一个元素的引用。好处是您仍然可以在恒定时间内删除元素,从哈希图中删除是恒定时间,在这种情况下,从链表中删除是因为您有对该条目的引用。您仍然可以在恒定时间内插入,因为哈希映射插入是恒定的,链表通常不是,但在这种情况下,您可以恒定时间访问链表中的某个点,因此您可以在恒定时间内插入,最后检索是恒定时间因为您只需要为它处理结构的哈希映射部分。


请记住,这样的数据结构并非没有成本。由于所有额外的引用,哈希图的大小将显着增加。每个主要方法都会稍微慢一些(如果重复调用它们可能很重要)。并且数据结构的间接性(不确定这是否是一个真正的术语:P)增加了,尽管这可能没什么大不了的,因为引用保证指向哈希映射内的东西。


由于这种结构的唯一优点是它可以保持秩序,因此在使用时要小心。另外,在阅读答案时请记住,我不知道这是它的实现方式,但如果给定任务,我会这样做。


oracle docs 上有一句话证实了我的一些猜测。

此实现与 HashMap 的不同之处在于,它维护一个双向链表,贯穿其所有条目。

来自同一网站的另一个相关引用。

该类提供所有可选的 Map 操作,并允许 null 元素。与 HashMap 一样,它为基本操作(添加、包含和删除)提供恒定时间性能,假设哈希函数在桶中正确地分散元素。由于维护链表的额外费用,性能可能略低于 HashMap,但有一个例外:迭代 LinkedHashMap 的集合视图所需的时间与映射的大小成正比,而不管其容量如何. HashMap 的迭代可能更昂贵,需要的时间与其容量成正比。

【讨论】:

  • LinkedList 中删除不是在恒定时间内完成的。如果您有对要删除的元素的引用,则只能在恒定时间内完成,否则,您必须遍历列表才能找到它。此外,迭代 LinkedHashMap 比迭代 HashMap 更有效,但 LinkedHashMap 的内存效率较低。
  • @SteveP。我看到了我只为插入而明确的问题
  • 是的。我也想知道!恒定时间操作将是相同的,因为它扩展了HashMap,对吗?但我不明白它说的部分:LinkedHashMap requires time proportional to the size of the map, regardless of its capacity
【解决方案5】:

hashCode 将通过哈希函数映射到任何桶。如果hashCode 中存在冲突而不是HashMap,则通过链接解决此冲突,即它将值添加到链接列表中。以下是执行此操作的代码:

for (Entry<K,V> e = table[i]; e != null; e = e.next) {
392             Object k;
393             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
394         `enter code here`        V oldValue = e.value;
395                 e.value = value;
396                 e.recordAccess(this);
397                 return oldValue;
398             }
399         }

您可以清楚地看到它遍历了链表,如果找到键,则将旧值替换为新的 else 附加到链表。

但是LinkedHashMapHashMap 的区别在于LinkedHashMap 维护了插入顺序。来自docs

这个链表定义了迭代顺序,通常是键插入映射的顺序(插入顺序)。请注意,如果将键重新插入到地图中,则插入顺序不会受到影响。 (如果在 m.containsKey(k) 将在调用之前立即返回 true 时调用 m.put(k, v),则将键 k 重新插入到映射 m 中)。

【讨论】:

  • 这不是他所问问题的答案
猜你喜欢
  • 2011-03-02
  • 1970-01-01
  • 2019-05-20
  • 2017-05-18
  • 2019-06-12
  • 2015-06-20
  • 2011-02-11
  • 2016-12-12
  • 1970-01-01
相关资源
最近更新 更多