LinkedHashMap源码分析

Posted wuqinglong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LinkedHashMap源码分析相关的知识,希望对你有一定的参考价值。

目录

概述

LinkedHashMap还是比较简单的, 相对于HashMap, 它是有序的, 那么问题就来了, 它是怎么保持有序的? 它直接继承于HashMap, 重写或增加了一些新的关于保持Map有序的方法, 至于扩容或是数据结构等都于HashMap一样, 下面我们重点分析它是怎么保持有序的这问题.

此外, LinkedHashMap还提供了按照最近访问有序还是按照插入顺序有序(属性: accessOrder), 默认为false(插入顺序有序), 可以通过LinkedHashMap的构造方法将其改为true(最近访问有序).

如何保证有序(敲黑板了)

想要保证有序, 那么元素之间就得有关系, 比如LinkedList, LinkedHashMap的思想也是同理, 只不过它在保证HashMap的数据结构下, 增加了元素之间的联系.

它通过以下几点保证了有序:

  1. 扩展了HashMap的Node内部类, 增加了before,after两个属性
  2. 重写了newNode方法
  3. 重写了HashMap中的三个回调方法

1. 扩展了HashMap的Node内部类

static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

增加了两个属性, 使用这两个属性可以保持元素之间的关系.

有一个问题就是我们重写了HashMap的Node类, 那么HashMap在链表转为树的时候会不会出现类转换异常呢?

答案就在HashMap的TreeNode类上, 它继承了LinkedHashMap.Entry, 所以No problem.

2. 重写了newNode和newTreeNode方法

在HashMap的putVal方法中, 新增元素必然需要创建节点, 这里重写了HashMap的newNode方法, 并且调用了内部方法(linkNodeLast), 来维护新增元素与原集合中最后一个元素的关系.

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;
}

// link at the end of list
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    
    // 获取最后一个元素
    LinkedHashMap.Entry<K,V> last = tail;
    
    // 令最后一个元素为新元素
    tail = p;
    
    // 如果最后一个元素为null(p为集合中第一个节点)
    if (last == null) {
        // head也为p
        head = p;
    } else {
        // 使p的上一个元素为原集合中的最后一个元素
        p.before = last;
        
        // 原集合中最后一个元素的下一个元素为p
        last.after = p;
    }
}

是不是So Easy, 就是维护链表之间的关系.

3. 重写了HashMap中的三个回调方法

// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

通过注释得知, 这方法的定义是为了让LinkedHashMap进行回调使用的.

这三个方法综合来说, 就是为了维护在发生元素的变动时, 对他们之间的关系进行维护.

3-1. afterNodeAccess

目的是将最后访问的元素移动到last, 注意: 只是改变Node之间的连接关系, 并不会改变元素的位置.

通过方法名可知, 这是在元素进行访问之后进行调用的.

源码如下:

void afterNodeAccess(Node<K,V> e) { // move node to last

    LinkedHashMap.Entry<K,V> last;
    
    // accessOrder默认是false, 可以通过构造方法进行设置为true.
    // 并且当前访问的元素不是last
    if (accessOrder && (last = tail) != e) {
    
        // 获取当前元素, 其前驱以及其后继
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            
        // 因为要把当前元素置为last, 所以它的after为null.
        p.after = null;
        
        // 当前元素没有前驱, 所以head为它的后继
        if (b == null)
            head = a;
        else  // 否则, 其前驱的after为其后继
            b.after = a;
            
        // 后继不为null的话, 后继的前驱即是其前驱.
        if (a != null)
            a.before = b;
        else // 既然 (last = tail) != e 成立了, 那么这里的a应该不会出现为null的情况.
            last = b;
            
        // 
        if (last == null)
            head = p;
        else {
            // 将 last 和 p 进行互换
            p.before = last;
            last.after = p;
        }
        
        // 令tail为p, 最后访问的元素
        tail = p;
        ++modCount;
    }
}

3-2. afterNodeInsertion

这里是在put元素之后发生的调用, 似乎永远不会进行处理.

源码如下:

void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

通过源码发现, removeEldestEntry这个方法永远返回false, 所以条件永远不会成立, 所以nothing.

3-3. 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;
}

就是将其前驱的后继指向其后继, 将其后继的前驱指向为其前驱.

遍历

HashMap的遍历思想和LinkedHashMap的遍历思想是不同的.

HashMap的遍历思想

在数组上从0下标开始遍历, 如果是null, 则+1, 去下一个位置查找元素; 如果不是null, 返回该元素, 然后如果该位置有链表或者红黑树时, 会进入到链表或者红黑树中查找元素返回.

LinkedHashMap的遍历思想

从head开始遍历, 也就是头结点元素, 通过元素之间的关系, 一直遍历到尾结点tail.

显然是链表的思想.

总结

  1. LinkedHashMap是对HashMap的扩展, 增加有序的实现.
  2. 提供插入顺序排序和最近访问排序

不要因为知识简单就忽略, 不积跬步无以至千里.

以上是关于LinkedHashMap源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Java源码分析LinkedHashMap源码分析

Java源码分析LinkedHashMap源码分析

JDK1.8源码分析之LinkedHashMap

死磕 java集合之LinkedHashMap源码分析

JAVA集合LinkedHashMap及其源码分析

LinkedHashMap源码分析与LRU实现