LinkedHashMap 源码解读

1 结构简介

他继承自hashMap,在hashMap的基础上添加了一个双向链表的结构。双向链表决定迭代顺序 (迭代顺序在下文中具体介绍)。LinkedHashMap 的add,contains,remove的时间复杂度为O(1),但是性能比HashMap稍差,因为在其内部需要维护链表。
迭代器迭代全部元素时,需要的时间正比于size(而HashMap正比于capacity) 性能稍优于HashMap。
和HashMap一样,存在两个影响性能的参数:initial capacity 和 load factor。但是大的初始容量对Linkedhashmap的影响没有hashmap那么大,因为linkedhashmap的迭代器和capacity无关(从其源码中构建LinkedHashIterator时可以看出来,其实按照双向链表迭代的)。

对mapping的增加或删除,以及对迭代顺序的影响都认为是对结构的修改。在插入数据时,如果是以插入顺序为迭代顺序(insertion-ordered linked hash maps)的linkedhashmap只修改了现在map中包含的entry的值,不认为对结构做了修改。如果以访问顺序为迭代顺序(access-ordered)的linkedhashmap,仅仅一个get方法都认为修改了结构。

2 非线程安全

不是线程同步的,自己要控制数据的同步。也可以使用Collection.synchronizedMap。

Map m = Collections.synchronizedMap(new LinkedHashMap(...));

3 特殊的构造函数

LinkedHashMap 提供了一个构造函数 LinkedHashMap(int,float,boolean)。如果boolean设置为true 这个函数创建一个 按照最后访问迭代的linked hash map。从最近最少使用的到最近最多使用的(from least-recently accessed to most-recently accessed)。如果为false则迭代顺序为数据的插入顺序。

上面的boolean 即为其每部变量accessOrder。

LinkedHashMap map = new LinkedHashMap<String,String>(16,0.75f,true);
        for (int i = 0; i < 10; i ++) {
            map.put(String.valueOf(i), String.valueOf(i));
        }

        System.out.println(map);
        map.get("2");
        System.out.println(map);
        map.get("5");
        System.out.println(map);
        map.get("3");
        System.out.println(map);

输出结果

{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}

{0=0, 1=1, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9, 2=2}

{0=0, 1=1, 3=3, 4=4, 6=6, 7=7, 8=8, 9=9, 2=2, 5=5}

{0=0, 1=1, 4=4, 6=6, 7=7, 8=8, 9=9, 2=2, 5=5, 3=3}

最先访问的放到最后面

对于其内部的变化需要注意一下几点:

  • 当在构造函数中设定了第3个参数为true时,调用如下方法会使内部元素移动到迭代器的尾部。
    put,putIfAbsent,get,getOrDefault,compute,computeIfAbsent,computIfPresent,merge。

  • replace 方法只有value被替换时才会导致元素位置的变化。

  • putAll方法会影响所有在给定map中的key-value对,影响的顺序按照给定map中迭代器的顺序。

  • 其他方法都不会产生对entry的访问。在 collection-views层面的操作不会影响迭代器的顺序。

  • 调用containsValue不会影响内部链表的顺序。

以上方法导致链表中值发生变化的根本原因是因为在访问数据时调用了afterNodeRemoval,在添加数据时,调用了linkNodeLast将刚访问的数据放到了双链表的尾部。

几个比较重要的内部函数

以下几个方法在HashMap中的也有对应的方法定义,但是没有实现,主要是为LinkedhashMap提供接口,由其实现。这是模板设计模式

linkNodeLast

    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }

在每次添加节点后,会调用该方法将新添加的节点维护到链表的尾部。

private void transferLinks(LinkedHashMap.Entry<K,V> src,
                               LinkedHashMap.Entry<K,V> dst) {
        LinkedHashMap.Entry<K,V> b = dst.before = src.before;
        LinkedHashMap.Entry<K,V> a = dst.after = src.after;
        if (b == null)
            head = dst;
        else
            b.after = dst;
        if (a == null)
            tail = dst;
        else
            a.before = dst;
    }

该函数将链表中的src节点替换为dst,在替换Map中的节点时调用。

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

该函数在调用Map的remove方法,删除一个节点时,将从map中移除的节点从双向链表中移除。

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

该函数在map中的某个节点被访问后,如果accessOrder = true ,将被访问的节点移动到双向链表的尾部。

LinkedHashMap迭代器的核心代码即为其内部类LinkedhashInterator。在调用hashNext时,将当前的值存入临时变量current中,然后将指针往下移动一位。
在调用next时,直接返回current的值。

小知识:

如果覆写removeEldestEntry方法,其返回值为true时会移除陈腐的(stale) mappings,即链表比较靠头部的mapping。

posted @ 2018-01-13 16:43  arax  阅读(230)  评论(0编辑  收藏  举报