首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Java 容器源码分析之 LinkedHashMap(3)

Java 容器源码分析之 LinkedHashMap(3)

如何维护插入序和访问序?在 LinkedHashMap 中,所有的 Entry 都被串联在一个双向链表中。从上一节的代码中可以看到,每次在新建一个节点时都会将新建的节点链接到双向链表的末尾。这样从双向链表的尾部向头部遍历就可以保证插入顺序了,头部节点是最早添加的节点,而尾部节点则是最近添加的节点。那么,访问顺序要怎么实现呢?
之前我们在分析 HashMap 的源码的时候,在添加及更新、查找、删除等操作中可以看到 afterNodeAccess、afterNodeInsertion、afterNodeRemoval 等几个方法的调用,不过在 HashMap 中这几个方法中没有任何操作。实际上,这几个方法就是供 LinkedHashMap 的重写的,我们不妨看一下在 HashMap 中这几个方法的声明:
1
2
3
4
// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
在 LinkedHashMap 中对这几个方法进行了重写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//移除节点的回调函数
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;
}

//插入节点的回调函数
//构造函数中调用,evict为false
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    //first是头元素,也是最老的元素
    //在插入序中,就是最先插入的元素
    //在访问序中,就是最远被访问的元素
    //这里removeEldestEntry(first)始终返回true,即不删除最老的元素
    //如果是一个容量固定的cache,可调整removeEldestEntry(first)的实现
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        //不是构造方法中
        //头元素不为空
        //要删除最老的元素
        //在LinkedHashMap的实现中,不会进入这里
        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;
    //如果是访问序,且当前节点并不是尾节点
    //将该节点置为双向链表的尾部
    if (accessOrder && (last = tail) != e) {
        //p 当前节点, b 前驱结点, a 后继结点
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.after = null; //设为尾节点,则没有后继
        if (b == null)
            head = a; //p是头节点,调整后其后继结点变为头节点
        else
            b.after = a;//p不是头节点,前驱和后继结点相连
        if (a != null)
            a.before = b;
        else
            last = b;//应该不会出现这种情况,p是尾节点
        if (last == null)
            head = p;
        else {
            //将p置于尾节点之后
            p.before = last;
            last.after = p;
        }
        tail = p;//调整tail指向
        ++modCount;//结构性改变
    }
}
在插入节点、删除节点和访问节点后会调用相应的回调函数。可以看到,在 afterNodeAccess方法中,如果该 LinkedHashMap 是访问序,且当前访问的节点不是尾部节点,则该节点会被置为双链表的尾节点。即,在访问序下,最近访问的节点会是尾节点,头节点则是最远访问的节点。
返回列表