Java中的LinkedHashMap源码分析
作者:爱喝咖啡的程序员
一. 基本原理和优缺点
LinkedHashMap能记录你插入元素的顺序,在遍历时,能按照插入的顺序给你遍历出来。
LinkedHashMap是HashMap的子类,所以基本的操作与hashmap类似。不过呢,在插入、删除、替换key-value对的时候,LinkedHashMap会维护一个链表结构,专门用来记录key-value对的顺序。当我们遍历LinkedHashMap时,就能按照顺序把key-value来遍历出来。
所以啊,别看LinkedHashMap的名字中带有Linked,其实它的底层仍然是数组实现的。
当删除元素时,它会在双向链表的尾部删除节点,当查询元素时,实际上是在迭代双向链表,从头节点开始迭代。用于维护双向链表的顺序。
这里面有一个非常核心的参数: accessOrder。
二. 源码分析
2.1 put(K key, V value) 初次插入
绝大部分代码逻辑与hashmap完全一样,只不过,LinkedHashMap自己重写了newNode()方法。
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e); linkNodeLast(p); return p; }
将节点封装成了LinkedHashMap.Entry对象,然后使用linkNodeLast()挂载节点。
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { LinkedHashMap.Entry<K,V> last = tail; tail = p; if (last == null) head = p; else { p.before = last; last.after = p; } }
这里可以看到,LinkedHashMap内部维护了两个指针head和tail,分别指向顺序链表的头结点和尾结点。
每当我们向LinkedHashMap内新增一个key-value,就会在顺序链表的末尾挂载一个顺序节点。这个挂载的过程其实就是对双向链表新增一个节点。
2.2 put(K key, V value) 覆盖已经存在的key
如果LinkedHashMap中已经插入了key1-value1,此时我们插入key1-value2,会产生什么后果呢?
答案是,key1在LinkedHashMap内的插入顺序保持不变,但是value被覆盖。
关于覆盖的操作,由于我们熟悉HashMap的源码,所以立刻可以锁定到hashmap的putVal()内如下代码块:
if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; }
如果在插入之前,能找到这个key对应的节点,那么就用新的value覆盖这个节点内的旧value,接着执行afterNodeAccess(e)。
注意,LinkedHashMap重写了afterNodeAccess(e)。
void afterNodeAccess(Node<K,V> e) { // move node to last LinkedHashMap.Entry<K,V> last; if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.after = null; if (b == null) head = a; else b.after = a; if (a != null) a.before = b; else last = b; if (last == null) head = p; else { p.before = last; last.after = p; } tail = p; ++modCount; } }
LinkedHashMap有一个非常重要的参数,accessOrder ,它可以通过入参,经过构造函数传入LinkedHashMap。accessOrder默认等于false,这就意味着,你覆盖或者查询这个key,都不会改变key在链表里的顺序。当它等于true时,就截然相反了,每当你覆盖或者查询这个key,LinkedHashMap就会把这个key挪动到顺序链表的末尾。
2.3 remove
大部分的逻辑仍然走的是hashmap的removeNode(),只不过LinkedHashMap重写了afterNodeRemoval( )。
void afterNodeRemoval(Node<K,V> e) { // unlink LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.before = p.after = null; if (b == null) head = a; else b.after = a; if (a == null) tail = b; else a.before = b; }
到此这篇关于Java中的LinkedHashMap源码分析的文章就介绍到这了,更多相关LinkedHashMap源码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!