JDK1.8源码解读之 LinkedHashMap

178 阅读10分钟

前言

  • Map接口的 哈希表和链表实现,具有可预测的迭代顺序。
  • 此实现与 HashMap 的不同之处在于,它维护一个遍历其所有条目的双向链接列表。
  • 此链表定义了迭代顺序,通常是将键插入映射中的顺序(插入顺序)。请注意,如果将键重新插入到映射中,则插入顺序不会受到影响。
  • 可以生成和原来的map 元素顺序一致的map
  • 提供了特殊的 LinkedHashMap(int,float,boolean)构造函数,以创建linked hash map。其迭代顺序是其条目的从最近最少访问,到最近最多访问。
  • 这种映射非常适合构建LRU缓存。
  • 调用 put,putIfAbsent,get,getOrDefault,compute,computeIfAbsent, computeIfPresent 或 merge方法导致对相应条目的访问(假设调用完成后该条目存在)。
  • 只有替换了值,replace才会会导致对条目的访问。
  • putAll 方法为指定映射中的每个映射生成一个条目访问,其顺序为指定映射的条目集迭代器提供键-值映射。
  • 没有其他方法可以生成条目访问。特别是,对集合视图的操作不会影响backing map的迭代顺序。
  • 该类提供了所有Map的额外操作,并且允许null元素。
  • 假设hash函数在桶中合理分散了元素,该类像HashMap一样,对基本操作,add, contains, remove 提供了恒定时间性能。
  • 性能比HashMap稍微差一点,因为add的花费包含了链表的操作。
  • 但是也有一个例外:
  • 在 LinkedHashMap 的集合视图上进行迭代无论 map 的容量如何,都需要与map的size成比例的时间。
  • 在 HashMap 上进行迭代可能会更昂贵,需要的时间与其capacity成比例。
  • 链接的哈希映射具有两个影响其性能的参数:初始容量 和 负载系数。
  • 它们的定义与 HashMap 完全相同。
  • 但是请注意,与 HashMap 相比,此类为初始容量选择过高的值的惩罚不那么严重,因为此类的迭代时间不受容量的影响。
  • 请注意,此实现未同步。
  • 如果多个线程同时访问链接的哈希映射,并且至少有一个线程在结构上修改该映射,则必须在外部对其进行同步。
  • 通常,通过在自然封装map的某个对象上进行同步来完成此操作。
  • 如果不存在这样的对象,则应使用 Collections.synchronizedMap 方法“包装”map。
  • 最好在创建时完成此操作,以防止意外的非同步访问 map:
  • Map m = Collections.synchronizedMap(new LinkedHashMap(...));
  • 结构修改是添加或删除一个或多个映射的任何操作,或者在访问排序的链接的哈希映射的情况下,会影响迭代的顺序。
  • 在插入顺序链接的哈希表中,仅更改与映射中已包含的键关联的值不是结构上的修改。
  • 在按访问顺序排列的linked hash map中,仅使用 get 查询该映射是结构上的修改。
  • 该类的所有集合视图方法返回的集合的 iterator 方法返回的迭代器为 fail-fast :
  • 如果随时对结构进行了结构修改,创建迭代器后,通过迭代器自身的 remove 方法以外的任何方式,迭代器都会引发 ConcurrentModificationException。
  • 因此,面对并发修改,迭代器会快速干净地失败,而不会在未来的不确定时间内冒任意,不确定的行为的风险 * 注意,迭代器的快速失败行为无法得到保证。因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。
  • 快速失败的迭代器会尽最大努力抛出ConcurrentModificationException。
  • 因此,编写依赖于此异常的程序的正确性是错误的:迭代器的快速失败行为仅应用于检测错误。
  • 由此类的所有集合视图方法返回的集合的spliterator方法返回的spliterators为 后期绑定,快速失败并另外报告? 翻译捉急

源码

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

    /*
     * 
     * 补充说明。该类的先前版本在内部结构上有些不同。
     * 由于超类HashMap现在将树用于其某些节点,因此LinkedHashMap.Entry类现在被视为中间节点类,也可以转换为树形式。
     * 此类的名称LinkedHashMap.Entry在其当前上下文中以多种方式令人困惑,但是无法更改。
     * 否则,即使未将其导出到此程序包之外,
     * 也已知某些现有源代码在对removeEldestEntry的调用中依赖符号解析的特殊情况规则,
     * 该规则抑制了由于用法不明确引起的编译错误。因此,我们保留名称以保留未修改的可编译性。
     * 节点类的更改还需要使用两个字段(head,tail),而不是指向标头节点的指针,以维护双向链接的前/后列表。
     * 此类在访问,插入和删除时也曾使用过不同样式的回调方法。
     */

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

    private static final long serialVersionUID = 3801124242820219131L;

    transient LinkedHashMap.Entry<K,V> head;

    transient LinkedHashMap.Entry<K,V> tail;

    final boolean accessOrder;

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

    void reinitialize() {
        super.reinitialize();
        head = tail = null;
    }

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

    Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
        LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
        LinkedHashMap.Entry<K,V> t =
            new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next);
        transferLinks(q, t);
        return t;
    }

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

    TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
        LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
        TreeNode<K,V> t = new TreeNode<K,V>(q.hash, q.key, q.value, next);
        transferLinks(q, t);
        return t;
    }

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

    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }

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

    void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
            s.writeObject(e.key);
            s.writeObject(e.value);
        }
    }

    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();
        accessOrder = false;
        putMapEntries(m, false);
    }

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

    public boolean containsValue(Object value) {
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
            V v = e.value;
            if (v == value || (value != null && value.equals(v)))
                return true;
        }
        return false;
    }

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

    public V getOrDefault(Object key, V defaultValue) {
       Node<K,V> e;
       if ((e = getNode(hash(key), key)) == null)
           return defaultValue;
       if (accessOrder)
           afterNodeAccess(e);
       return e.value;
   }

    public void clear() {
        super.clear();
        head = tail = null;
    }

    /**
     *      * 如果此映射应删除其最旧的条目,则返回true。
     * 在将新条目插入到映射后,由put和putAll调用此方法。
     * 每次添加新条目时,它为实施者提供了删除最旧条目的机会。
     * 如果映射表示缓存,则此功能很有用:它允许映射通过删除陈旧条目来减少内存消耗。
     * 使用示例:此替代操作将使地图最多可以容纳100个条目,然后每次添加新条目时都会删除最旧的条目,从而保持100个条目的稳定状态。 
     */
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

    /**
     * 返回此映射中包含的键的{@link Set}视图。该集合由map支持,因此对map的更改会反映在集合中,反之亦然。
     * 如果在对集合进行迭代时修改了映射(通过迭代器自己的remove操作除外),则迭代的结果不确定。
     * 该集合支持元素删除,该元素通过Iterator.remove,Set.remove,removeAll,retainAll和clear操作从映射中删除相应的映射。
     * 它不支持add或addAll操作。与{@code HashMap}相比,其{@link Spliterator}通常提供更快的顺序性能,但并行性能却差很多。
     * 
     */
    public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new LinkedKeySet();
            keySet = ks;
        }
        return ks;
    }

    final class LinkedKeySet extends AbstractSet<K> {
        public final int size()                 { return size; }
        public final void clear()               { LinkedHashMap.this.clear(); }
        public final Iterator<K> iterator() {
            return new LinkedKeyIterator();
        }
        public final boolean contains(Object o) { return containsKey(o); }
        public final boolean remove(Object key) {
            return removeNode(hash(key), key, null, false, true) != null;
        }
        public final Spliterator<K> spliterator()  {
            return Spliterators.spliterator(this, Spliterator.SIZED |
                                            Spliterator.ORDERED |
                                            Spliterator.DISTINCT);
        }
        public final void forEach(Consumer<? super K> action) {
            if (action == null)
                throw new NullPointerException();
            int mc = modCount;
            for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
                action.accept(e.key);
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }

    /**
     *返回此映射中包含的值的{@link Collection}视图。
     * 集合由map支持,因此对地图的更改会反映在集合中,反之亦然。
     * 如果在对集合进行迭代时修改了映射(通过迭代器自己的remove操作除外),则迭代的结果是不确定的。
     * 集合支持元素删除,该元素通过Iterator.remove,Collection.remove,removeAll,retainAll和clear操作从映射中删除相应的映射。
     * 它不支持add或addAll操作。与{@code HashMap}相比,其{@link Spliterator}通常提供更快的顺序性能,但并行性能却差很多。 
     */
    public Collection<V> values() {
        Collection<V> vs = values;
        if (vs == null) {
            vs = new LinkedValues();
            values = vs;
        }
        return vs;
    }

    final class LinkedValues extends AbstractCollection<V> {
        public final int size()                 { return size; }
        public final void clear()               { LinkedHashMap.this.clear(); }
        public final Iterator<V> iterator() {
            return new LinkedValueIterator();
        }
        public final boolean contains(Object o) { return containsValue(o); }
        public final Spliterator<V> spliterator() {
            return Spliterators.spliterator(this, Spliterator.SIZED |
                                            Spliterator.ORDERED);
        }
        public final void forEach(Consumer<? super V> action) {
            if (action == null)
                throw new NullPointerException();
            int mc = modCount;
            for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
                action.accept(e.value);
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }

    /**
     * 返回此映射中包含的映射的{@link Set}视图。
     * 该集合由地图支持,因此对地图的更改会反映在集合中,反之亦然。
     * 如果在对集合进行迭代时修改了映射(除非通过迭代器自己的remove操作或通过迭代器返回的映射条目上的setValue操作),则迭代的结果是不确定的。
     * 该集合支持元素删除,该元素通过Iterator.remove,Set.remove,removeAll,retainAll和clear操作从映射中删除相应的映射。
     * 它不支持add或addAll操作。
     * 与{@code HashMap}相比,其{@link Spliterator}通常提供更快的顺序性能,但并行性能却差很多。
     * 
     *
     * @return a set view of the mappings contained in this map
     */
    public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
    }

    final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
        public final int size()                 { return size; }
        public final void clear()               { LinkedHashMap.this.clear(); }
        public final Iterator<Map.Entry<K,V>> iterator() {
            return new LinkedEntryIterator();
        }
        public final boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>) o;
            Object key = e.getKey();
            Node<K,V> candidate = getNode(hash(key), key);
            return candidate != null && candidate.equals(e);
        }
        public final boolean remove(Object o) {
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>) o;
                Object key = e.getKey();
                Object value = e.getValue();
                return removeNode(hash(key), key, value, true, true) != null;
            }
            return false;
        }
        public final Spliterator<Map.Entry<K,V>> spliterator() {
            return Spliterators.spliterator(this, Spliterator.SIZED |
                                            Spliterator.ORDERED |
                                            Spliterator.DISTINCT);
        }
        public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
            if (action == null)
                throw new NullPointerException();
            int mc = modCount;
            for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
                action.accept(e);
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }

    // Map overrides

    public void forEach(BiConsumer<? super K, ? super V> action) {
        if (action == null)
            throw new NullPointerException();
        int mc = modCount;
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
            action.accept(e.key, e.value);
        if (modCount != mc)
            throw new ConcurrentModificationException();
    }

    public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
        if (function == null)
            throw new NullPointerException();
        int mc = modCount;
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
            e.value = function.apply(e.key, e.value);
        if (modCount != mc)
            throw new ConcurrentModificationException();
    }

    // Iterators

    abstract class LinkedHashIterator {
        LinkedHashMap.Entry<K,V> next;
        LinkedHashMap.Entry<K,V> current;
        int expectedModCount;

        LinkedHashIterator() {
            next = head;
            expectedModCount = modCount;
            current = null;
        }

        public final boolean hasNext() {
            return next != null;
        }

        final LinkedHashMap.Entry<K,V> nextNode() {
            LinkedHashMap.Entry<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            current = e;
            next = e.after;
            return e;
        }

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }

    final class LinkedKeyIterator extends LinkedHashIterator
        implements Iterator<K> {
        public final K next() { return nextNode().getKey(); }
    }

    final class LinkedValueIterator extends LinkedHashIterator
        implements Iterator<V> {
        public final V next() { return nextNode().value; }
    }

    final class LinkedEntryIterator extends LinkedHashIterator
        implements Iterator<Map.Entry<K,V>> {
        public final Map.Entry<K,V> next() { return nextNode(); }
    }
}