LinkedHashMap简介

480 阅读2分钟

HashMap是无序的,如果希望有序地存储key-value时,就需要使用LinkedHashMap了。

LinkedHashMap的继承关系如下:

LinkedHashMap使用

构造方法

LinkedHashMap有5个构造方法。

构造方法参数 initialCapacity loadFactor accessOrder
16 0.75 false
int initialCapacity 参数指定 0.75 false
int initialCapacity, float loadFactor 参数指定 参数指定 false
int initialCapacity, float loadFactor, boolean accessOrder 参数指定 参数指定 参数指定
Map<? extends K, ? extends V> m 根据m大小 0.75 false

accessOrder为false表示按照插入顺序,为true表示按照访问顺序。最新插入或访问的节点会被放在链表最后。

get方法

LinkedHashMap在自己的get方法里调用了HashMap的getNode方法,并做了后置处理——如果按照访问顺序排序,就调整顺序。

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

通过entrySet遍历的时候调用的HashMap.Node的方法,不会对LindedHashMap的顺序造成影响。

put已经存在的key也算做访问了这个key。

put方法

LinkedHashMap并没有重写HashMap的put方法,而是重写了newNode方法。

对比一下:

// HashMap
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
    return new Node<>(hash, key, value, next);
}
// LinkedHashMap
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);
    return p;
}

new了一个节点,然后用linkNodeLast方法处理,顾名思义将节点连在尾部。

// link at the end of list
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;
    }
}

总结

LinkedHashMap是在HashMap的基础上,每个节点上增加了两个指针,分别指向它之前和之后的节点,排序方式由accessOrder决定。

对比一下HashMap和LinkedHashMap的节点数据结构:

// HashMap
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
    // 省略方法
}
// LinkedHashMap
static class Entry<K,V> extends HashMap.Node<K,V> { // 继承自HashMap的Mode
    Entry<K,V> before, after; // 仅仅多了两个指针
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

注意到这两内部静态类都是默认(空白)访问修饰符,因此遍历的时候只能通过Map.Entry。Map.Entry是内部接口,访问权限是public的。

accessOrder=false时,put的时候将节点连在链表最后面,再次put已有的key不会改变节点顺序。

accessOrder=true时,get的时候修改节点顺序,put已有的key亦会修改节点顺序。

LinkedHashMap实现LRU

重写LinkedHashMap预留的removeEldestEntry方法实现LRU。

public class MyLru {

    public static void main(String[] args) {
        int size = 3;
        Map<Integer, Integer> map = new LinkedHashMap<Integer, Integer>(size, 1, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                if (super.size() > size) {
                    return true;
                }
                return false;
            }
        };
        map.put(1, 1);
        map.put(2, 2);
        map.put(3, 3);
        map.put(4, 4);
        map.put(5, 6);

        for (HashMap.Entry<Integer, Integer> entry : map.entrySet()) {
            System.out.println(entry);
        }
    }
}