[toc]

LinkedHashMap结构

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {
    transient LinkedHashMap.Entry<K,V> head;
    transient LinkedHashMap.Entry<K,V> tail;
    /**
     * The iteration ordering method for this linked hash map:
     * <tt>true</tt> for access-order, <tt>false</tt> for insertion-order.
     */
    final boolean accessOrder;
}

Entry结构

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

get查询

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

LikedHashMap的get()方法底层是调用的HashMap的getNode()方法,不同的是如果accessOrder为true(access-order iteration),会执行afterNodeAccess(e);操作。

afterNodeAccess方法实现

void afterNodeAccess(Node<K,V> e);

LinkedHashMap重写了HashMap中afterNodeAccess这个空方法,将节点e移动到链表的尾部。

这里注意遍历LinkedHashMap的时候,iterator是从head节点开始遍历的,所以如果是设置了access-order,靠近尾节点的是最近使用到的,而靠近头节点的(先被遍历到)是最近最少使用到的。

put添加元素

LinkedHashMap没有对HashMap的put()方法进行重写,直接使用HashMap的put()方法添加元素。

如果是哈希表中已经存在了key相等的节点,那么修改value后执行上述的afterNodeAccess方法;如果原来不存在相同的key,那么执行重写的newNode()方法,最后如果需要扩容,扩容结束后执行afterNodeInsertion(evict);

重写的newNode方法

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    linkNodeLast(p); // link at the end of list
    return p;
}

这里跟HashMap的区别就是创建了一个LinkedHashMap.Entry,并将该节点链接到链表的尾部。

用来实现LRU的afterNodeInsertion方法

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

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

afterNodeInsertion方法在put方法返回之前被回调,该方法在HashMap中是个空方法,就是为了给LinkedHashMap重写添加功能用的。

这个方法的逻辑是,如果需要删除且removeEldestEntry(first)返回true,那么就删除头节点,也就是最早添加的节点(如果是access-order,就是最近最少使用的节点)。

而removeEldestEntry方法默认实现就是返回false,所以默认是不会删除节点的。只有我们自己需要以基础LinkedHashMap的方式实现LRU Cache时需要重写removeEldestEntry方法。

利用LinkedHashMap实现LRU Cache

public class LRUCache extends LinkedHashMap<Integer, Integer> {
    private int capacity;

    public LRUCache_146(int capacity) {
        // accessOrder 为 true,默认是 insertion-order
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    public int get(int key) {
        return super.getOrDefault(key, -1);
    }

    public void put(int key, int value) {
        super.put(key, value);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        return size() > capacity;
    }
}

Tagged #Java集合.