数据结构 - LinkedHashMap

简介

LinkedHashMap扩展了HashMap,是HashMap的二次封装,加入了一个单独链表存所有数据,并且完整保留HashMap结构。

LinkedHashMap类
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

继承HashMap,在HashMap原有结构基础上又维护了一个链表

重要内部类LinkedHashMap.Entry<K,V>
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中也用到,HashMap的TreeNode就是继承此类(是不是很绕)

LinkedHashMap属性
// 链表头结点
transient LinkedHashMap.Entry<K,V> head;
// 链表尾结点
transient LinkedHashMap.Entry<K,V> tail;
// LRU算法相关  false 基于插入顺序,true 基于访问顺序 
final boolean accessOrder;
LinkedHashMap构造函数
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;
}

从构造函数中可以看出,LinkedHashMap几乎完全使用HashMap的构造方法初始化(参照HashMap类篇),至于初始长度和加载因子也是延用HashMap,LinkedHashMap类自身只关注链表结构,数字加链表结构交给父类去维护

根据键取值
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;
}

实际上就是调用HashMap的getNode方法, afterNodeAccess(e)方法只是为了维护顺序,除了这个方法LinkedHashMap没有添加删除方法,那么他是怎么维护链表结构的呢?是否还记得在HashMap篇有几个方法要放在LinkedHashMap讲

void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

这几个方法出现在HashMap元素增加或减少之后,就是为了给hashMap子类使用

父类元素变动后方法
// HashMap删除节点后
void afterNodeRemoval(Node<K,V> e) {
    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删除元素成功后会调用此方法afterNodeRemoval

// 插入成功删除头节点
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);
    }
}

afterNodeInsertion方法是在哈希表中插入了一个新节点时调用的,它会把链表的头节点删除掉,删除的方式是通过调用HashMap的removeNode方法。我们要使用此功能必须重写removeEldestEntry方法

// accessOrder为true时将节点移到最后
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;
        if (a != null)
            a.before = b;
        else
            last = b;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

当accessOrder设置为true时,把当前节点e移至链表的尾部,在HashMap的putVal方法中,会调用此方法

posted @ 2020-03-31 20:54  源码猎人  阅读(478)  评论(0编辑  收藏  举报