LinkedHashMap源码(JDK1.8)

1. 概述

LinkedHashMap继承了HashMap,并在其基础上维护了一条双向链表,用来保证顺序访问。

2. 源码分析

2.1 内部类和属性

LinkedHashMap的内部类继承了Node,并根据需要增加了before,after属性。这两个属性你肯定似曾相识,在LinkedList中使用过。其实他们的功能其实是一样的,定位前一个或后一个entry

// 头结点
transient LinkedHashMap.Entry<K,V> head;
// 尾节点
transient LinkedHashMap.Entry<K,V> tail;
// 排序方式,true:访问顺序排序(LRU) false:插入顺序排序
final boolean accessOrder;

// 其它的属性都在hashmap中,这里就不一一列出了

// LinkedHashMap的内容存储类
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.2 构造方法

通过下面的代码可以看出,LinkedHashMap的构造方法基本是调用父类构造来完成的,自己只是完成accessOrder属性操作(也就是排序方式,默认false)。

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

2.3 插入方法和钩子方法

LinkedHashMap的元素获取是调用HashMapput方法,不过LinkedHashMap通过重写HashMap的钩子方法来实现一些自己的逻辑。

// HashMap
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;
        }
    }
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);		// 钩子方法
    return null;
}

// 钩子方法的定义(在HashMap中为空实现,留给子类重写逻辑)
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

// LinkedHashMap
// 节点访问后,移动元素e到链尾,频繁访问的元素就会落在尾部
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    // accessOrder为null且tail节点不是e,才会进入方法
    if (accessOrder && (last = tail) != e) {
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, 
        // b为e的前驱节点,a为p的后继节点
        b = p.before, a = p.after;
        p.after = null;
        if (b == null)
            head = a;		// b=null说明e为头结点,则将后继a设为头节点
        else
            b.after = a;	// b!=null,将b的后继设置为a
        if (a != null)
            a.before = b;	// a!=null,将a的前驱设置为b
        else
            last = b;		// a=null则将last设置为b
        if (last == null)
            head = p;		// p为头结点
        else {
            p.before = last;
            last.after = p;	// 设置p和last的链接关系
        }
        tail = p;		// 设置p为尾节点
        ++modCount;
    }
}

// 在put方法调用到这里evict为true
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);	// 在HashMap中,这里不再解释
    }
}

// 这个方法总是返回false,你可能会疑惑这个鬼东西是否有问题。
// 其实这个方法是一个钩子方法,留给你自己来实现决定是否删除first(LRU算法实现)
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

// 节点删除后断开链接
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;
}

2.4 获取方法

了解了hashmapgetNode方法和上边的钩子方法,那么这里看起来就比较无脑了,没啥逻辑。

public V get(Object key) {
    Node<K,V> e;
    // 调用hashmap的getNode获取entry
    if ((e = getNode(hash(key), key)) == null)
        return null;
    // 决定是否更新e到链尾
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

2.5 移除方法

移除方法更没营养,调用HashMapremove方法时调用了一下afterNodeRemoval方法。

2.6 LRU缓存实现

主要内容其实就是重写LinkedHashMapremoveEldestEntry方法,Mybatis也用LinkedHashMap实现LRU算法,也是这个套路,懂就行了

// 摘抄自石杉的LRU实现
class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int CACHE_SIZE;

    /**
     * 传递进来最多能缓存多少数据
     *
     * @param cacheSize 缓存大小
     */
    public LRUCache(int cacheSize) {
        // true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。
        super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
        CACHE_SIZE = cacheSize;
    }

    /**
     * 钩子方法,通过put新增键值对的时候,若该方法返回true
     * 便移除该map中最老的键和值
     */
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        // 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。
        return size() > CACHE_SIZE;
    }
}

3. 总结

总结好难,不总结了。

你可能感兴趣的:(java基础)