我们在使用HashMap的过程中会发现一个问题,对于这个集合,我们好像没有办法让其按照我们想要的顺序输出。

针对这个需求,就诞生了LinkedHashMap。

LinkedHashMap集成了HashMap,然后有两个自己的独特成员:

private transient Entry<K,V> header;
private final boolean accessOrder;

LinkedList是一个双向链表,header是其头部,是一个初始化为-1的节点,这也是一个链表常见的技巧。

void init() {
    header = new Entry<>(-1, null, null, null);
    header.before = header.after = header;
}

而这位accessOrder同学就是主角啦!

对LinkedHashMap初始化时,可以选择是否初始化这个成员,

public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    accessOrder = false;
}

public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
    accessOrder = false;
}

public LinkedHashMap() {
    super();
    accessOrder = false;
}

public LinkedHashMap(Map<? extends K, ? extends V> m) {
    super(m);
    accessOrder = false;
}

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}
View Code

如果不初始化,则默认是false,此时保持放入元素先后的顺序不变化,即按照你插入的顺序排列和输出。

如果选择初始化为true,则按照访问顺序排列。

从get操作中可以验证这个结论

public V get(Object key) {
    Entry<K,V> e = (Entry<K,V>)getEntry(key);
    if (e == null)
        return null;
    e.recordAccess(this);
    return e.value;
}

看一下recordAccess()函数,这是Entry的方法:

void recordAccess(HashMap<K,V> m) {
    LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
    if (lm.accessOrder) {
        lm.modCount++;
        remove();
        addBefore(lm.header);
    }
}

如果LinkedHashMap的accessOrder为true,就先remove,然后添加到双向链表的尾部(双向链表header的before节点就是末尾节点),这样就完成了按访问顺序排列

至于Entry内部的操作,例如添加、删除之类的,就是简单的双向链表操作的方法,直接上代码了,折叠之:

private static class Entry<K,V> extends HashMap.Entry<K,V> {
    // These fields comprise the doubly linked list used for iteration.
    Entry<K,V> before, after;

    Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
        super(hash, key, value, next);
    }

    /**
     * Removes this entry from the linked list.
     */
    private void remove() {
        before.after = after;
        after.before = before;
    }

    /**
     * Inserts this entry before the specified existing entry in the list.
     */
    private void addBefore(Entry<K,V> existingEntry) {
        after  = existingEntry;
        before = existingEntry.before;
        before.after = this;
        after.before = this;
    }

    /**
     * This method is invoked by the superclass whenever the value
     * of a pre-existing entry is read by Map.get or modified by Map.set.
     * If the enclosing Map is access-ordered, it moves the entry
     * to the end of the list; otherwise, it does nothing.
     */
    void recordAccess(HashMap<K,V> m) {
        LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
        if (lm.accessOrder) {
            lm.modCount++;
            remove();
            addBefore(lm.header);
        }
    }

    void recordRemoval(HashMap<K,V> m) {
        remove();
    }
}
View Code

 上面看的是访问节点,那么添加节点呢?

void addEntry(int hash, K key, V value, int bucketIndex) {
    super.addEntry(hash, key, value, bucketIndex);

    // Remove eldest entry if instructed
    Entry<K,V> eldest = header.after;
    if (removeEldestEntry(eldest)) {
        removeEntryForKey(eldest.key);
    }
}

直接继承HashMap的添加节点方法,但下面又调用了一个删除最老节点的操作,看一下:

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

咦?那这样不是永远都返回false么?哪还有什么意义?

当然有意义,我们可以重写这个函数,为期设定删除最老节点的条件,例如size()>100之类的。

到这里,应该就意识到了,这个LinkedHashMap好像可以实现鼎鼎大名的LRU缓存算法吧?答对了,不过这个在以后细说。

好了,LinkedHashMap的分析暂时就到这里。

posted on 2016-03-08 23:49  岳阳楼  阅读(609)  评论(0编辑  收藏  举报