LinkedHashMap

LinkedHashMap继承自HashMap, 它的大部分功能都维持了HashMap的原样, 同时在此基础上又维护了一个双向链表结构. 通过对链表的操作, 可以实现LRU、FIFO等功能.

LinkedHashMap中的节点

// 继承自 HashMap.Node, 并将其拓展为双向链表结构
static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
    // 增加了这两个属性, 实现双向链表结构
    LinkedHashMapEntry<K,V> before, after;
    LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

LinkedHashMap中新增的属性

// 双向链表的头
transient LinkedHashMapEntry<K,V> head;
// 双向链表的尾
transient LinkedHashMapEntry<K,V> tail;
// 当accessOrder为false时, 双向链表的顺序就是节点插入的顺序;
// 当accessOrder为true时, 每当节点被访问或修改时, 会将该节点移到链表的尾部. 利用这一原理, 可以实现LRU功能
final boolean accessOrder;

LinkedHashMap构造方法

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

可以发现, 构造方法基本和HashMap一致, 唯一多了一个 accessOrder 的设置.

LinkedHashMap中重写的方法

增加元素

LinkedHashMap没有重写HashMap的put方法, 回顾一下HashMap的putVal.

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 ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        ...
        if (e != null) { 
            ...
            // 空方法, 由其子类LinkedHashMap实现
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    // 空方法, 由其子类LinkedHashMap实现
    afterNodeInsertion(evict);
    return null;
}

在添加新元素时, 如果在hash表中没有找到hash值和key都相匹配的节点, 会调用 newNode 方法添加新节点, 并最终调用一个叫 afterNodeInsertion 的空方法; 如果找到了匹配的节点, 则直接替换其value, 并最终调用一个叫 afterNodeAccess 的空方法.

对于需要新建节点的情况, LinkedHashMap重写了 newNode 和 afterNodeInsertion 方法

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    // 创建的节点是 LinkedHashMapEntry
    LinkedHashMapEntry<K,V> p = new LinkedHashMapEntry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}
...
private void linkNodeLast(LinkedHashMapEntry<K,V> p) {
    // 保存当前链表的尾部
    LinkedHashMapEntry<K,V> last = tail;
    // 将新节点设置为链表的尾部
    tail = p;
    if (last == null)
        // 链尾为空, 表示是第一个元素, 则直接设置为链头
        head = p;
    else {
        // 将新元素放在链尾
        p.before = last;
        last.after = p;
    }
}
...
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMapEntry<K,V> first;
    // 如果 evict为true, 且链头不为空, 且 removeEldestEntry 方法也返回true
    if (evict && (first = head) != null &&  (first)) {
        // 删除链头指向的节点
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}
...
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

对于无需新建节点, 而是直接替换其value的情况, LinkedHashMap重写了 afterNodeAccess 方法

// 在改变元素节点的value后, 会调用该方法, 将节点移到链表的尾部
// 在访问元素节点后, 也可能会调用该方法, 将节点移到链表的尾部
void afterNodeAccess(Node<K,V> e) { // move node to last
    // 原来的链尾
    LinkedHashMapEntry<K,V> last;
    // accessOrder为true, 且当前访问的节点不是链尾
    if (accessOrder && (last = tail) != e) {
        // 将该节点强转为LinkedHashMapEntry类型, 同时保存它在链表中的上一个节点和下一个节点
        LinkedHashMapEntry<K,V> p = (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
        // 因为要将该节点移到尾部, 所以将其after置为空
        p.after = null;
        if (b == null)
            // b为null, 表示p就是原来的链头, 所以将直接将它的下一个节点设置为新的链头
            head = a;
        else
            // 将p从原来在链表上的位置移除 (即将p的前置节点的后置节点, 设置为p的后置节点 ...)
            b.after = a;
        if (a != null)
            // 如果p的后置节点不为空, 则将其前置节点设置为p的前置节点
            a.before = b;
        else
            // 如果p的后置节点为null, 表示p就是链尾. 此时设置last为p的前置节点
            last = b;
        if (last == null)
            // 如果last为null, 表示此事链表上没有数据, 直接将p设置为链头
            head = p;
        else {
            // 将p放在链表的尾部
            p.before = last;
            last.after = p;
        }
        // 将链尾设置为p
        tail = p;
        ++modCount;
    }
}

删除元素

LinkedHashMap同样也没有重写 remove 方法, 和上面的添加元素相似, HashMap在成功删除元素后, 会调用一个叫 afterNodeRemoval的空方法, LinkedHashMap也重写了该方法

// 在删除了节点e后, 会调用此方法将其从链表上移除
void afterNodeRemoval(Node<K,V> e) { // unlink
    LinkedHashMapEntry<K,V> p = (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
    // 将其前置节点和后置节点都设置为null, 解除引用关系
    p.before = p.after = null;
    if (b == null)
        // b为null, 表示p就是原来的链头, 因此直接将其后置节点设置为新的链头
        head = a;
    else
        // 将b的后置节点设置为a
        b.after = a;
    if (a == null)
        // a为null, 表示p就是原来的链尾, 因此将其前置节点设置为新的链尾
        tail = b;
    else
        // 将a的前置节点设置为b
        a.before = b;
}

查找元素

LinkedHashMap重写了 get 和 getOrDefault 方法

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    // 如果 accessOrder 为true, 则调用 afterNodeAccess将查到的节点移到链尾
    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;
    // 如果 accessOrder 为true, 则调用 afterNodeAccess将查到的节点移到链尾
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

containsKey & containsValue

LinkedHashMap只重写了containsValue 方法.

public boolean containsValue(Object value) {
    // 直接遍历链表 (HashMap中是同时遍历数组和链表)
    for (LinkedHashMapEntry<K,V> e = head; e != null; e = e.after) {
        V v = e.value;
        if (v == value || (value != null && value.equals(v)))
            return true;
    }
    return false;
}

  转载请注明: 四月一号 LinkedHashMap

 上一篇
LruCache LruCache
使用缓存有助于提高应用的体验, 但是内存的大小是有限的, 所以有必要考虑如何更好更合理的对缓存进行管理. LruCache是一种常用的缓存策略, 它利用 LinkedHashMap 的特点, 在缓存大小超出阈值时, 对最老的缓存数据进行清
2019-04-11
下一篇 
HashMap HashMap
HashMap作为日常开发中用的最多的集合之一, 非常有必要对它的内部原理进行一下了解, 同时源码里的一些写法也是非常值得学习的. HashMap中的一些常量和变量// 默认初始化容量 16 static final int DEFAULT
2019-04-04
  目录