一 概述
深入理解Java集合中的源代码,可以帮助我们更好地了解大佬的意图,规避不必要的bug。
源码中的一段注释,提取关键信息
Hash table and linked list implementation of the Map interface, with predictable iteration order. This implementation differs from HashMap in that it maintains a doubly-linked list running through all of its entries. This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order). Note that insertion order is not affected if a key is re-inserted into the map. (A key k is reinserted into a map m if m.put(k, v) is invoked when m.containsKey(k) would return true immediately prior to the invocation.)
从注释中,我们可以先了解到 LinkedHashMap 是通过哈希表和链表实现的,它通过 维护一个链表来保证对哈希表迭代时的有序性,而这个有序是指键值对插入的顺序。 另外,当向哈希表中重复插入某个键的时候,不会影响到原来的有序性。也就是说, 假设你插入的键的顺序为 1、2、3、4,后来再次插入 2,迭代时的顺序还是 1、2、 3、4,而不会因为后来插入的 2 变成 1、3、4、2。(但其实我们可以改变它的规则, 使它变成 1、3、4、2)
LinkedHashMap 的实现主要分两部分,一部分是哈希表,另外一部分是链表。哈希 表部分继承了 HashMap,拥有了 HashMap 那一套高效的操作,所以我们要看的就是 LinkedHashMap 中链表的部分,了解它是如何来维护有序性的。
LinkedHashMap 的大致实现如下图所示,当然链表和哈希表中相同的键值对都是指 向同一个对象,这里把它们分开来画只是为了呈现出比较清晰的结构。
二 属性
在看属性之前,我们先来看一下 LinkedHashMap 的声明:
1 | public class LinkedHashMap<K,V> extends HashMap<K,V> imple ments Map<K,V> |
从上面的声明中,我们可以看见 LinkedHashMap 是继承自 HashMap 的,所以它已 经从 HashMap 那里继承了与哈希表相关的操作了,那么在 LinkedHashMap 中,它 可以专注于链表实现的那部分,所以与链表实现相关的属性如下。
1 | //LinkedHashMap 的链表节点继承了 HashMap 的节点,而且每个节点都包含 |
三 方法
如果仔细看过 HashMap 源码的话,会发现 HashMap 中有如下三个方法:
1 | // Callbacks to allow LinkedHashMap post-actions |
如果没有注意到注释的解释的话,可能会很奇怪为什么会有三个空方法,而且 有不少地方还调用过它们。其实这三个方法表示的是在访问、插入、删除某个节点 之后,进行一些处理,它们在 LinkedHashMap 都有各自的实现。LinkedHashMap 正 是通过重写这三个方法来保证链表的插入、删除的有序性。
1 afterNodeAccess方法
1 | void afterNodeAccess(Node<K,V> e) { // move node to last |
这段代码的意思简洁明了,就是把当前节点 e 移至链表的尾部。因为使用的是双向 链表,所以在尾部插入可以以 O(1)的时间复杂度来完成。并且只有当accessOrder
设置为 true 时,才会执行这个操作。在 HashMap 的 putVal 方法中,就调用了这个 方法。
2 afterNodeInsertion方法
1 | void afterNodeInsertion(boolean evict) { // possibly remov e eldest |
afterNodeInsertion 方法是在哈希表中插入了一个新节点时调用的,它会把链表的头 节点删除掉,删除的方式是通过调用 HashMap 的 removeNode 方法。想一想,通过 afterNodeInsertion 方法和 afterNodeAccess 方法,是不是就可以简单的实现一个基于 最近最少使用(LRU)的淘汰策略了?当然,我们还要重写 removeEldestEntry 方法, 因为它默认返回的是 false。
3 afterNodeRemoval方法
1 | void afterNodeRemoval(Node<K,V> e) { // unlink |
这个方法是当 HashMap 删除一个键值对时调用的,它会把在 HashMap 中删除的那 个键值对一并从链表中删除,保证了哈希表和链表的一致性。
4 get方法
1 | public V get(Object key) { |
,LinkedHashMap 的 get 方法就是这么简单,因为它调用的是 HashMap 的 getNode 方法来获取结果的。并且,如果你把 accessOrder 设置为 true,那么在获取到值之后,还会调用 afterNodeAccess 方法。这样是不是就能保证一个 LRU 的算法了.
5 put和remove方法
在 LinkedHashMap 的源码中没有找到 put 方法,这就说明了它并没有重写 put 方 法,所以我们调用的 put 方法其实是 HashMap 的 put 方法。因为 HashMap 的 put 方 法中调用了 afterNodeAccess 方法和 afterNodeInsertion 方法,已经足够保证链表的有 序性了,所以它也就没有重写 put 方法了。remove 方法也是如此。