LinkedHashMap源代码阅读

Posted yutingliuyl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LinkedHashMap源代码阅读相关的知识,希望对你有一定的参考价值。

LinkedHashMap

LinkedHashMap内部採用了散列表和链表实现Map接口,并能够保证迭代的顺序,和HashMap不同,其内部维护一个指向全部元素的双向链表,其决定了遍历的顺序,一般是元素插入的顺序进行迭代,只是元素又一次插入顺序不会受到影响。

LinkedHashMap提供一个特殊的构造函数,实现了每次迭代返回近期使用的元素,这个特性能够用于构建LRU缓存。

此外removeEldestEntry(Map.Entry)方法能够被子类覆盖用于推断在加入元素的时候什么时候能够删除元素。

LinkedHashMap性能相同受到初始容量和装填因子的影响,对于基本操作(add,contains,remove)在常数时间内,其性能比HashMap略微低。因为须要额外代价维护链表;只是其遍历性能为O(size)高于HashMapO(capacity)。

LinkedHashMap实现

类定义

直接继承了HashMap

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>

成员

private transient Entry<K,V> header; //用于遍历的双向链表表头

/**
 * The iteration ordering method for this linked hash map:
 * true: for access-order, false: for insertion-order
 */
private final boolean accessOrder;

Entry内部类继承了HashMap.Entry<K,V>类,添加了两个指针before和after用于维护遍历顺序,实际上Entry有三个指针父类本身有个next指针用于当发生元素冲突时指向的下一个元素。

由此能够看出用于遍历的双向链表直接加在Entry上面,这样有效节约了空间,实际仅仅比HashMap多了2*size个引用+1个头结点空间消耗。

before和after这两个引用在外部类调用put或remove时,调用其相关方法进行维护(recordAccess和recordRemoval等)。

 private static class Entry<K,V> extends HashMap.Entry<K,V> {
    Entry<K,V> before, after;

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

    private void remove() {
        before.after = after;
        after.before = before;
    }
    //existingEntry之前加入当前节点
    private void addBefore(Entry<K,V> existingEntry) {
        after  = existingEntry;
        before = existingEntry.before;
        before.after = this;
        after.before = this;
    }
    //由父类HashMap的put方法调用,若是acessOrder。则加入到双向链表的头结点后面;本身get方法也会触发调用
    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();
    }
 }

put方法是继承自父类HashMap,重写了当须要加入元素时候调用的addEntry方法,相同是在相应桶的链表头结点后面加入。加入完以后不是直接进行resize推断,而是推断是否要删除旧的元素,这种方法默认返回false,用户能够重写这种方法用于确定缓存的淘汰机制。

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

    // Remove eldest entry if instructed, else grow capacity if appropriate
    Entry<K,V> eldest = header.after;
    if (removeEldestEntry(eldest)) {
        removeEntryForKey(eldest.key);
    } else {
        if (size >= threshold)
            resize(2 * table.length);
    }
}

void createEntry(int hash, K key, V value, int bucketIndex) {
    HashMap.Entry<K,V> old = table[bucketIndex];
    Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
    table[bucketIndex] = e;
    e.addBefore(header); //每次都是在相应桶链表的開始处加入
    size++;
}

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

get方法

public V get(Object key) {
    Entry<K,V> e = (Entry<K,V>)getEntry(key);
    if (e == null)
        return null;
    e.recordAccess(this); //实现LRU
    return e.value;
}
//重写父类方法,效率更高O(size)
public boolean containsValue(Object value) {
    // Overridden to take advantage of faster iterator
    if (value==null) {
        for (Entry e = header.after; e != header; e = e.after)
            if (e.value==null)
                return true;
    } else {
        for (Entry e = header.after; e != header; e = e.after)
            if (value.equals(e.value))
                return true;
    }
    return false;
}

视图和迭代器

LinkedHashMap相同继承了创建3个集合类视图:键集合、值集合、键值对集合的方法。因为额外维护一个双向链表保证迭代顺序,重写了相关视图的迭代器实现,LinkedHashIterator通过直接迭代链表的header指针来实现指定顺序遍历。

Iterator<K> newKeyIterator()   { return new KeyIterator();   }
Iterator<V> newValueIterator() { return new ValueIterator(); }
Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }

private class KeyIterator extends LinkedHashIterator<K> {
    public K next() { return nextEntry().getKey(); }
}

private class ValueIterator extends LinkedHashIterator<V> {
    public V next() { return nextEntry().value; }
}

private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {
    public Map.Entry<K,V> next() { return nextEntry(); }
}

private abstract class LinkedHashIterator<T> implements Iterator<T> {
    Entry<K,V> nextEntry    = header.after;
    Entry<K,V> lastReturned = null;
    int expectedModCount = modCount;

    public boolean hasNext() {
        return nextEntry != header;
    }

    public void remove() {
        if (lastReturned == null)
            throw new IllegalStateException();
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();

        LinkedHashMap.this.remove(lastReturned.key);
        lastReturned = null;
        expectedModCount = modCount;
    }

    Entry<K,V> nextEntry() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        if (nextEntry == header)
            throw new NoSuchElementException();

        Entry<K,V> e = lastReturned = nextEntry;
        nextEntry = e.after;
        return e;
    }
}

总结

  1. LinkedHashMap继承自HashMap。相关基本操作性能略低于HashMap。因为须要额外代价维护链表。其遍历操作是通过操作该双向链表实现,而非内部散列表数组。因此性能为O(size)比HashMapO(capacity)更高。
  2. 支持两种顺序遍历:元素插入顺序(反复put不算)和近期使用优先顺序(调用put和get类似LRU),默认是依照元素插入顺序遍历。通过构造函数传入true能够实现近期使用优先遍历。每次put或get操作时,将该元素直接又一次放置到链表头结点后面来实现近期使用优先遍历。

  3. LinkedHashMap并没有又一次创建一个新的链表来实现顺序遍历,而是在每一个Entry上多加了两个指针来决定遍历顺序。有效节约了空间消耗。

    实际仅仅比HashMap多了2*size个引用+1个头结点空间消耗。

  4. LinkedHashMap支持元素淘汰策略,能够通过重写removeEldestEntry方法。来决定调用put时候是否须要删除旧的元素。LinkedHashMap能够用于实现LRU缓存,并自己定义元素淘汰策略。

以上是关于LinkedHashMap源代码阅读的主要内容,如果未能解决你的问题,请参考以下文章

超实用的php代码片段

推荐net开发cad入门阅读代码片段

21个常用代码片段

源码阅读(24):Java中其它主要的Map结构——LinkedHashMap容器(下)

PHP 代码片段

源码阅读(23):Java中其它主要的Map结构——LinkedHashMap容器(上)