史上最全的JDK1.8LinkedHashMap底层源码分析

LinkedHashMap 继承自 HashMap,在 HashMap 基础上,通过维护一条双向链表,解决了 HashMap 不能随时保持遍历顺序和插入顺序一致的问题。除此之外,LinkedHashMap 对访问顺序也提供了相关支持。在一些场景下,该特性很有用,比如缓存。在实现上,LinkedHashMap 很多方法直接继承自 HashMap,仅为维护双向链表覆写了部分方法。所以,要看懂 LinkedHashMap 的源码,需要先看懂 HashMap 的源码。关于 HashMap 的源码分析,详见https://www.jianshu.com/p/2520bdcaf40f

构造方法

以空构造为例,就是调用hashMap的空构造方法,accessOrder为false表示不以访问时间为顺序维护,而是以默认的插入顺序为主

public LinkedHashMap() {
        super();
        accessOrder = false;
    }

super调用hashMap的空构造

public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

put方法

linkedHashMap是没有实现map的put方法,而是继承了hashMap,调用hashMap的put,然后重新覆写了hashMap的newNode方法

接下来我们看下newNode方法,先new出linkedHashMap的entry,是一个双向链表,然后调用linkNodeLast,将新增entry放入双向链表的尾部

Node newNode(int hash, K key, V value, Node e) {
        LinkedHashMap.Entry p =
            new LinkedHashMap.Entry(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }

linkNodeLast方法

private void linkNodeLast(LinkedHashMap.Entry p) {
    LinkedHashMap.Entry last = tail;
    tail = p;
    // last 为 null,表明链表还未建立
    if (last == null)
        head = p;
    else {
        // 将新节点 p 接在链表尾部
        p.before = last;
        last.after = p;
    }
}

然后hashMap有两个linkedHashMap覆写的方法,一个是afterNodeAccess方法,一个是afterNodeInsertion

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
       
        //此处省略代码
        ........
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

我们先来看afterNodeAccess方法,首先是判断accessOrder 为真也就是要以访问顺序为顺序维护,然后还要尾结点不等于待插入的节点,再对各个节点赋值,如下图所示

因为要将待插入的节点挪到最后,所以待插入节点的尾指针置为空
开始多个条件的判断:

待插入节点的头结点判断
  • b==null 也就是待插入节点的头指针是空,如果为空就代表待插入节点前面没节点,就直接将待插入节点的尾指针指向head,后面再将待插入节点的头指针指向a就可以了
  • b!=null,也就是待插入节点前面有节点,那就将待插入节点的尾指针指向它的头结点,后面将待插入节点的头指针指向尾结点的就可以
待插入节点的尾结点判断
  • a!=null,就是待插入节点有尾结点,就将该尾结点的的头指针指向待插入节点的头节点,后面将待插入节点的头指针指向尾结点的就可以
  • a==null,就让待插入节点的头结点给last,也就是最后元素,后面将待插入节点的头指针指向last结点就可以
last结点判断
  • last==null,就说明待插入节点的头结点判断中a!=null,也就是待插入节点没有头结点,还可以说明a==null,也就是它没有尾结点,由待插入节点的尾结点判断科研推断,则将待插入节点指向顶端节点
  • last!=null,待插入节点头指针指向last节点,last的尾结点指向待插入节点

以上判断完成后,经过tail = p将待插入节点指向尾结点,完成顺序调换,将待插入节点放到双向链表最后

void afterNodeAccess(Node e) { // move node to last
        LinkedHashMap.Entry last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry p =
                (LinkedHashMap.Entry)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;
        }
    }

再来看afterNodeInsertion方法,afterNodeInsertion方法用于移除链表中的最旧的节点对象,也就是链表头部的对象。但是在JDK1.8版本中,可以看到removeEldestEntry一直返回false,所以该方法并不生效。如果存在特定的需求,比如链表中长度固定,并保持最新的N的节点数据,可以通过重写该方法来进行实现。这里就不细讲了

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

removeNode方法里就是从根节点去查找移除节点,跟hashMap讲的一样,详见hashMap源码解析

final Node removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node[] tab; Node p; int n, index;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node node = null, e; K k; V v;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            else if ((e = p.next) != null) {
                if (p instanceof TreeNode)
                    node = ((TreeNode)p).getTreeNode(hash, key);
                else {
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

还会调用到afterNodeRemoval方法,这个方法是在前面删除节点后调整双链表的结构,也没啥好说的了

void afterNodeRemoval(Node e) { // unlink
        LinkedHashMap.Entry p =
            (LinkedHashMap.Entry)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;
    }

你可能感兴趣的:(史上最全的JDK1.8LinkedHashMap底层源码分析)