Android源码简单集合之LinkedHashMap

448 阅读6分钟

学习无止境,探索无止境

1. LinkedHashMap简介

LinkedHashMap继承自HashMap,所以拥有HashMap的所有特性,在HashMap的基础上新增了双向链表结构,实现对数据按照插入顺序或访问顺序 LinkedHashMap继承关系:

LinkedHashMap继承关系.png

2. LinkedHashMap结构

LinkedHashMap在HashMap的基础上,新增了双向链表结构,由LinkedHashMapEntry表示LinkedHashMap的节点。

  • LinkedHashMap中只有一条双向链表,包含map中所有元素
  • 双向链表中的节点,通过记录前置节点和后置节点连接整个链表
  • 新增的节点插入到双向链表尾部
  • 双向链表支持插入顺序排序或访问顺序排序

LinkedHashMap存储结构.png

3. LinkedHashMap源码

3.1. 成员属性

transient LinkedHashMapEntry<K,V> head;    // 双向链表中的表头,旧数据靠近表头

transient LinkedHashMapEntry<K,V> tail;    // 双向链表中的表尾,新插入数据放在表尾

final boolean accessOrder;                 // 是否按访问顺序排序,最近被使用的节点放在表尾

3.2. 内部节点

Entry继承自HashMap.Node节点,在它基础上新增了before和after,用于构建双向链表。

// LinkedHashMapEntry 继承自 HashMap.Node
static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
    // before: 前置节点
    // after: 后置节点
    LinkedHashMapEntry<K,V> before, after;
    LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

双向链表结构如下图:

LinkedHashMap双向链表结构.png

3.3. 构造方法

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

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

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

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

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}
  • 前四个构造方法,accessOrder为false,双向链表按照元素的插入顺序排序。
  • 最后一个构造方法支持设置accessOrder,accessOrder为true时,双向链表按照元素的访问顺序排序,最近被访问的元素放在表尾。

3.4. afterNodeRemoval方法

// HashMap源码removeNode节点方法,map中存在匹配的节点,则删除节点,最后调用afterNodeRemoval方法做节点删除后的操作,在LinkedHashMap中有用到
final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
        ...

        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            ...
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}

// 删除双向链表中的节点
void afterNodeRemoval(Node<K,V> e) {
    // 记录e节点为p
    // b记作p节点的前置节点
    // a记作p节点的后置节点
    LinkedHashMapEntry<K,V> p = (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
    // 清理删除节点的前置节点和后置节点,方便GC
    p.before = p.after = null;
    // 如果b==null成立,说明删除的p节点为head节点,将a节点置为新的head结点;否则,将a节点置为b节点的after节点
    if (b == null)
        head = a;
    else
        b.after = a;
    // 如果a==null成立,说明删除的p节点为tail节点,将a节点置为新的tail节点;否则,将b节点置为a节点的before节点
    if (a == null)
        tail = b;
    else
        a.before = b;
}

LinkedHashMap双向链表删除.png afterNodeRemoval方法主要用于删除双向链表结构中的元素,删除流程如下: 假设删除节点为entry2节点记作p节点,entry1节点为entry2节点的before记作b节点,entry3节点为entry2节点的after记作a节点。

  • 如果b==null,则p节点是head结点,将a节点置为新的head结点
  • 如果b!=null,则p节点不是head节点,将a节点置为b节点的after节点
  • 如果a==null,则p节点是tail节点,将a节点置为新的tail节点
  • 如果a!=null,则p节点不是tail节点,将b节点置为a节点的before节点

3.6. afterNodeInsertion方法

// HashMap源码putVal节点方法,map中新插入一个元素后,调用afterNodeInsertion方法,做节点插入后的操作,LinkedHashMap中用到
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    ...
    afterNodeInsertion(evict);
    return null;
}

// map中插入新节点时的操作,主要用于LinkedHashMap中
void afterNodeInsertion(boolean evict) {
    LinkedHashMapEntry<K,V> first;
    // head节点不为null,removeEldestEntry返回true,则从双向链表删除head节点
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

afterNodeInsertion方法用于插入节点之后的操作,当removeEldestEntry返回true,删除head节点。

3.7. afterNodeAccess方法

// HashMap源码putVal节点方法,map中更新已存在的元素,调用afterNodeAccess方法,做节点更新后的操作,LinkedHashMap中用到
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;
            // 更新map中的元素之后
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ...
}

// 更新map中节点或访问map中的节点时,将更新或访问的节点移动到双向链表的尾部,主要用于LinkedHashMap中
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMapEntry<K,V> last;
    // 按照访问顺序排序时 且 e不是在链表尾部
    if (accessOrder && (last = tail) != e) {
        // 记录e节点为p
        // b记作p节点的前置节点
        // a记作p节点的后置节点
        LinkedHashMapEntry<K,V> p =
            (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
        // 将p节点的after节点置为null,因为p节点将成为tail节点
        p.after = null;
        // 如果b==null成立,说明p节点是head节点,将a节点置为新的head节点,否则,将b节点的after指向a节点
        if (b == null)
            head = a;
        else
            b.after = a;
        // 如果 a!=null成立,说明p节点有后置节点,将a节点的before指向b节点;否则,将临时的last节点指向b节点
        if (a != null)
            a.before = b;
        else
            last = b;
        // 如果last==null成立,说明p为head节点且p为tail节点,head节点指向p节点;否则,p的before节点指向last节点,last节点的after指向p节点
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        // tail节点指向p
        tail = p;
        ++modCount;
    }
}

LinkedHashMap双向链表更新.png afterNodeAccess方法主要用于更新双向链表结构中的顺序,更新流程如下:

假设更新节点为entry2节点,entry1节点为entry2节点的before记作b节点,entry3节点为entry2节点的after记作a节点。

  • 如果b==null,则p节点是head节点,将a节点置为新的head节点
  • 如果b!=null,则p节点不是head节点,将b节点的after指向a节点
  • 如果a==null,则p节点是tail节点,将临时的last节点指向b节点
  • 如果a!=null,则p节点不是tail节点,将a节点的before指向b节点
  • 如果last==null
    1. 说明 b==null(即p是head节点)且 a==null(即p是tail节点)
    2. 上述条件成立,将p节点置为新的head节点
  • 如果last!=null,则p的before节点需要指向last节点,last节点的after需要指向p节点
  • 最后将tail节点指向p节点

3.8. get方法

// 如果map中有该节点,且双向链表按照访问顺序排序,则通过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;
}

结语: LinkedHashMap的主要操作已经分析完毕,LinkedHashMap的数据存储结构和HashMap一样,在其基础上增加了双向链表结构,通过accessOrder来控制对双向链表的排序,默认为插入顺序排序,当accessOrder为true时,按照访问顺序排序,在LruCache应用中采用了访问顺序排序。