LinkedHashMap

JDK版本jdk1.8.0_191 1

从继承体系的角度,LinkedHashMap是HashMap的子类

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

内部维护一个双向链表,维护插入顺序或者LRU顺序。

/**
* The head (eldest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> head;

/**
* The tail (youngest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> tail;

还有一个字段为accessOrder表示的是链表维护的是插入顺序还是LRU顺序
默认false表示维护插入顺序,而true则表示LRU顺序。

final boolean accessOrder;

那么它内部是怎么维护链表的顺序的呢?我们先回顾一下HashMap源码解读中的put,get方法,在putVal方法中有两个函数调用

afterNodeAccess(e);
afterNodeInsertion(evict);

从字面的意思上可以知道,afterNodeAccess是节点被访问后的函数调用,afterNodeInsertion是节点被插入后的函数调用;而这两个函数实际上在HashMap中是空实现
实际上,类似的函数总共有3个,它们都是空实现

// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

HashMap函数中对3个函数注释可以知道,这3个函数是空实现,并且它们是一种回调机制,特地提供给LinkedHashMap来后序回调用的;

所以LinkedHashMap继承自HashMap,一定重写了HashMap的这3个方法。

// 移除节点后调用,简单地移除就行
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;
}

// 插入节点后调用
// 当 removeEldestEntry() 方法返回 true 时会移除最晚的节点
// 也就是链表首部节点 first。

// removeEldestEntry() 默认为 false,
// 如果需要让它为 true,需要继承 LinkedHashMap 并且覆盖这个方法的实现,
// 这在实现 LRU 的缓存中特别有用,通过移除最近最久未使用的节点,
// 从而保证缓存空间足够,并且缓存的数据都是热点数据。
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);
    }
}

// 节点被访问后调用
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    // 如果accessOrder为true,也就是维护LRU链表
    // 如果指定维护LRU,当前访问的节点不在尾部
    // 则将此节点移动到尾部
    if (accessOrder && (last = tail) != e) {
        //              ↓
    	// ... -> b -> e(p) -> a -> ... 
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        // 首先after置为空,表示自己会被移动到尾部
        p.after = null;
        
        // 将p在链表处中断了的位置接起来
        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;
    }
}

这里要注意第二个方法中对removeEldestEntry(first)方法的解读。主要能够实现减少LRU对空间的巨大消耗。下面是实现的一个简单LRU。

// Reference to cyc2018
class LRUCache<K, V> extends LinkedHashMap<K, V> {

	private static final int MAX_ENTRIES = 3;
	
	protected boolean removeEldestEntry(Map.Entry eldest) {
		return size() > MAX_ENTRIES;
	}
	
	LRUCache() {
		super(MAX_ENTRIES, 0.75f, true);
	}
}
public static void main(String[] args) {
	LRUCache<Integer, String> cache = new LRUCache<>();
	cache.put(1, "a");
	cache.put(2, "b");
	cache.put(3, "c");
	cache.get(1);
	cache.put(4, "d");
	System.out.println(cache.keySet());
}
/** 运行结果
[3, 1, 4]
*/

你可能感兴趣的:(算法与数据结构,Java)