LinkedHashMap 源码解析(基于 JDK 1.8)

23 篇文章 1 订阅


源码环境: JDK 1.8

HashMap 源码解析

LinkedList 源码解析

LinkedHashMap 是 HashMap 的子类,在 HashMap 的基础上,对于每一个出现的节点 Node e,用双向链表来连接。可以理解为 LinkedHashMap = HashMap + LinkedList。

下方为一个 LinkedHashMap,由 head 到 tail 的顺序是 table[0] -> a -> table[3] -> c ->b。

在这里插入图片描述

1 主要属性

accessOrder,如果是 false 表示插入顺序,如果是 true 表示访问顺序。

具体说来,插入的时候两种情况都会将新元素插入最后,而访问的时候只有 true 会将访问的元素放在最后,false 不做操作。

head 是最老的节点,新插入或新访问的总是放在 tail。

有三种操作,即 put/get/remove,这里的 put/get/remove 不是指具体的方法,而是做相应操作的多个方法的统称。比如 put 相关方法指的是 put/putOrDefault/putVal。

  1. put 有两种情况:如果碰到同样的key (不管是否允许覆盖)相当于访问,否则相当于插入。

  2. get 相当于访问(这类方法被重写)。

  3. remove 相关方法只要执行了删除操作,则一定将这个节点从双向链表中删除。

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>
{
    transient LinkedHashMap.Entry<K,V> head; //最旧的节点
    transient LinkedHashMap.Entry<K,V> tail; //最新的节点
    final boolean accessOrder;// true是访问顺序,false是插入顺序
    ...
}

在这里插入图片描述

从上向下依次是 Map.Entry,HashMap.Node,LinkedHashMap.Entry 和 HashMap.TreeNode。

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

2 构造器

有5个构造器,除了第四个单独指定 accessOrder,其他的一律 accessOrder = false。具体由 HashMap 的构造器来实现。

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

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

public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }

3 钩子函数的实现

在 HashMap 中,有三个方法 afterNodeAccess,afterNodeRemoval,afterNodeInsertion 未实现,在 LinkedHashMap 中实现了这三个方法。

afterNodeAccess,afterNodeRemoval 是对双链表的节点进行操作,可以按照我在 LinkedList 中提到的原则来考虑:

通常先假设用到的节点都不是 null,写出整个处理过程。

由于 null 没法使用 after/before,在使用 node.after/node.before 时要判断一下 node 是不是 null, 是 null 则对应一些特殊情况,一般会重置头结点或尾结点。

在 LinkedList 中我画了一些图,可以辅助理解相关的操作。可以看看 LinkedList 源码解析

3.1 afterNodeAccess

为方便考虑,假定提到的节点都不重合,则有 5 个节点
<- head <-> b <-> p <-> a <-> last ->


1. 首先记
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;

2. 将 p 移动到最后,并与 last 连接
last.after = p;
tail = p;// 将 p 置为新的 tail
p.after = null;
p.before = last;

3. 将 a 和 b 连接起来
b.after = a;
a.before = b;

4.由于 last 和 e 不为 null,所以只在 a/b 为 null 时考虑一下。
如果 a 为 null,则 e 是 tail,这种情况不存在。
如果 b 为 null,则 e 是 head,所以移动后 a 是 head。

源码

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;
            // 这个判断没有意义,因为 e 不是 tail
            if (a != null)
                a.before = b;
            else
                last = b;
            // 这个判断没有意义,因为访问e,e 不为 null。
            // 则整个Map至少有一个节点,不可能last为null
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }

3.2 afterNodeRemoval

<- head <-> a <-> p <-> b <-> last ->

    
1. 首先记
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;

2. 将 p 与 a 和 b 的连接断开
p.before = null;
p.after = null;

3. 将 a 和 b 连起来
b.after = a;
a.before = b;

4.由于 e 不为 null,所以只在 a/b 为 null 时考虑一下。
如果 b 为 null,则 e 是 head,所以移动后 a 是 head。 
如果 a 为 null,则 e 是 tail,所以移动后 b 是 tail。

源码

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

3.3 afterNodeInsertion

这个方法是在插入后对 head 进行处理,如果满足条件(即 evict 为 true,存在至少一个元素,且允许删除第一个元素),则删除 head 所在的节点。

而按照最开始的说法,无论 accessOrder 怎么样,总是会将新的节点插入到最后,这个方法里面并没有做。

根据 debug 的结果,将新节点插入到最后的操作是在 newNode 和 newTreeNode 方法中,具体调用了 linkNodeLast 来实现新节点插入最后的。具体看第 6 节。

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

4 removeEldestEntry

默认删除最旧的(最前面) 的方法总是返回 false,即不删除,如果要实现删除功能,可以自己重写。

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

有一个类继承了 LinkedHashMap,使其满足 LRU 性质,最大元素为 MAX_CAPACITY,可如下实现。

@Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return this.size() > this.MAX_CAPACITY ;
    }

5 get

接下来看一下 get/put/remove 具体怎么实现链表的处理的。

重写了 get 和 getOrDefault 方法,在 HashMap 的基础上添加了判断 accessOrder 执行 afterNodeAccess 的操作。

public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)//新添加
            afterNodeAccess(e);//
        return e.value;
    }

public V getOrDefault(Object key, V defaultValue) {
       Node<K,V> e;
       if ((e = getNode(hash(key), key)) == null)
           return defaultValue;
       if (accessOrder)//新添加
           afterNodeAccess(e);//
       return e.value;
   }

6 put 相关

6.1 putVal

putVal 比较复杂,省略掉无关的东西,只看 LinkedHashMap 中有用的部分。

  1. 在第二个 if,如果当前桶是 null 会新建一个 Node。
  2. 如果当前节点是红黑树,putTreeVal 方法内部会新建一个 TreeNode 节点。
  3. 在对链表进行遍历的时候,创建新 Node。
  4. 如果找到了相同 key 的节点,此时 e != null,则不论是否替换,都会执行 afterNodeAccess,相当于访问了 e。
  5. 如果没找到相同的 key,则说明是 1 或 2 两种情况,此时执行 afterNodeInsertion。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ...
        // 当前桶的位置为null
        if ((p = tab[i = (n - 1) & hash]) == null) 
            // 创建新节点
            tab[i] = newNode(hash, key, value, null);
        else {
        ...
            if ...
            //如果是红黑树
            else if (p instanceof TreeNode)
                // 处理红黑树的情况
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            // 对链表进行处理
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        // 创建新节点
                        p.next = newNode(hash, key, value, null);
                        ...
                    }
                    ...
                }
                        
            if (e != null) {
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                // 访问后处理
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        // 插入后处理
        afterNodeInsertion(evict);
        return null;
    }

6.2 linkNodeLast

前面说过,afterNodeInsertion 并不管理将新的节点插入到链表最后。其实,在 LinkedHashMap 中,重写了 newNode 和 newTreeNode 方法,产生新的 Entry 或者 TreeNode 后,调用 linkNodeLast 将节点 p 插入到整个链表的最后。

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;
    }
    
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
        TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
        linkNodeLast(p);
        return p;
    }

// 将p放在tail后面,并重置tail
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;
        }
    }

7 remove

注意,在满足删除条件的情况下,先删除,然后调用 afterNodeRemoval 将对应 node 从链表中去掉。

final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
            if ...
            else if ...
            // 如果满足删除条件
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount;
                --size;
                // 在删除node后,将其也从链表中删除。
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

8 迭代器

使用的顺序是双链表不断 after 的顺序。

abstract class LinkedHashIterator {
        LinkedHashMap.Entry<K,V> next;
        LinkedHashMap.Entry<K,V> current;
        int expectedModCount;

        LinkedHashIterator() {
            next = head;
            expectedModCount = modCount;
            current = null;
        }

        public final boolean hasNext() {
            return next != null;
        }

        final LinkedHashMap.Entry<K,V> nextNode() {
            LinkedHashMap.Entry<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            current = e;
            next = e.after;//下一个元素使用 after。
            return e;
        }

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }

    final class LinkedKeyIterator extends LinkedHashIterator
        implements Iterator<K> {
        public final K next() { return nextNode().getKey(); }
    }

    final class LinkedValueIterator extends LinkedHashIterator
        implements Iterator<V> {
        public final V next() { return nextNode().value; }
    }

    final class LinkedEntryIterator extends LinkedHashIterator
        implements Iterator<Map.Entry<K,V>> {
        public final Map.Entry<K,V> next() { return nextNode(); }
    }

下面是我的公众号,Java与大数据进阶,分享 Java 与大数据笔面试干货,欢迎关注
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值