LinkedHashMap

概述

众所周知HashMap里面元素是无序的,而LinkedHashMap说直接点就是元素有序的HashMap。

初始化

/**
     * Constructs an empty <tt>LinkedHashMap</tt> instance with the
     * specified initial capacity, load factor and ordering mode.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @param  accessOrder     the ordering mode - <tt>true</tt> for
     *         access-order, <tt>false</tt> for insertion-order
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

LinkedHashMap的构造方法调用的父类HashMap的父类方法。然后新增了一个accessOrder这个参数。根据英文注释。当accessOrder为true时,元素按照访问顺序排列,为false的时候,元素按照插入顺序排列,默认值为false。我们后面会介绍这个参数的具体在哪里用到。

如何做到有序

 /**
     * HashMap.Node subclass for normal LinkedHashMap entries.
     */
    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);
        }
    }

我们看到集合中具体的元素对象Entry中多了两个成员变量before , after。由名称就可以猜出来,每个元素具有指向前一个元素和后一个元素的引用,所以LinkedHashMap元素是用双向链表实现的,并且还有指向头元素和尾元素的引用。我们可以从LinkedHashMap的成员变量中看到,如下:

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

添加元素

LinkedHashMap的没有复写父类的put方法(具体的put方法请看HashMap解析),而是直接调用父类的方法。但是复写了父类put方法中的几个子方法。

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

    TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
        TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
        linkNodeLast(p); //将添加的元素插入到末尾
        return p;
    }

JDK1.8中对对解决hash冲突做的很全面,最开始遇到冲突是用链表的形式也就是Entry对象,当链表具有一定的长度后会转换成红黑树也就是TreeNode对象。上面代码中可以看到当我们添加完一个元素后会调用linkNodeList()把元素插入到末尾。具体代码如下

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

由此我们可以知道,每当我们put一个元素的时候,会讲该元素插入到双向链表的末尾。遍历的时候会按照插入的顺序查找。

获取元素

LinkedHashMap复写了父类的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;
    }

方法中我们可以看到具体的获取元素仍然是调用父类的getNode方法(具体解析请看HashMap)。当我们获取到元素后,判断accessOrder的值,也就是我们初始化的传入的值,如果为true。会调用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;
        }
    }

代码仔细看一下还是能看懂,把访问到的元素移动到链表的末尾。这样就可以做到根据访问的顺序排列。

应用

根据LinkedHashMap可以根据访问顺序排序这一特性,我们可以基于它实现LRU缓存。LRU(Last Recently Used)最近最少使用的缓存。每当我们获取元素的时候会将该元素移动到链表的末尾。当我们内存已满的时候,链表最前面的元素即就是长时间没有被访问的,然后就可以释放了。以此来做到LRU。

results matching ""

    No results matching ""