1

本文以jdk1.8中LinkedHashMap.afterNodeAccess()方法为切入点,分析其中难理解、有价值的源码片段(类似源码查看是ctrl+鼠标左键的过程)。观光线路图:afterNodeAccess() --> afterNodeInsertion() --> removeEldestEntry() --> afterNodeRemoval() --> internalWriteEntries() ...

☞ afterNodeAccess()

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

//////////////////////////////// 涉及变量如下:

/**
 * 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;

/**
 * The iteration ordering method for this linked hash map: <tt>true</tt>
 * for access-order, <tt>false</tt> for insertion-order.
 *
 * @serial
 */
final boolean accessOrder;

上回在HashMap.afterNodeAccess()中说道,“是为LinkedHashMap留的后路”。如今行至于此,当观赏一方。首先需要了解的是LinkedHashMap相比HashMap多了有序性,由双向链表(before,after)实现。源码出现了一些全局变量:

accessOrder:true:按访问顺序排序(LRU),false:按插入顺序排序

head、tail:存放链表首尾

可见仅有accessOrder为true时,且访问节点不等于尾节点时,该方法才有意义。通过before、after重定向,将新访问节点链接为链表尾节点。

☞ 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()由于removeEldestEntry()所返回的false无执行意义。也就意味着如果想要让它有意义必须重写removeEldestEntry()。

如,使用LinkedHashMap实现一个简单的LRU(Least Recently Used)Cache。那么就应该重写removeEldestEntry(),当超出缓存容器大小时移除最老的首节点(这里不考虑并发问题,如下):

@Override  
public boolean removeEldestEntry(Map.Entry<K, V> eldest){       
    return size() > capacity;          
}  

☞ afterNodeRemoval()

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

afterNodeRemoval()方法相对简单,就是在删除后处理其对应链表前后关系(刨掉一截)。

☞ internalWriteEntries()

LinkedHashMap源码阅读总体门槛相对而言比HashMap,毕竟大多数底层put,get都由HashMap实现了。internalWriteEntries()相对来说比较突兀,如果你知道它在哪里起着什么样神秘的作用请在评论里告诉在下吧。[比心❤]

void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
    for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
        s.writeObject(e.key);
        s.writeObject(e.value);
    }
}

可通过这篇文章理解创建一个LinkedHashMap实例过程(图):

Java_LinkedHashMap工作原理 2017-05-04;

往期线路回顾:

【java源码一带一路系列】之HashMap.putAll()
【java源码一带一路系列】之HashMap.putVal()
【java源码一带一路系列】之HashMap.compute()


花田土著
190 声望34 粉丝

希望加入有意思的开发团队!