LinkedHashMap

LinkedHashMap 源码解析

Posted by Allen Vork on June 24, 2018

Synopsis

LinkedHashMap 是将 HashMap 和双向链表相结合的产物,拥有了 HashMap 的特性外还保持了迭代的顺序。LRU Cache 内部就是通过 LinkedHashMap 实现的。

LinkedHashMap 与 HashMap 有什么不同

  • 结点不同
    LinkedHashMap 的结点是继承自 HashMap.Node,并添加了 before 和 after 指针,用于维护节点的插入顺序,使得节点形成双向链表。
  • put 操作不同
    在执行 put 操作后,Entry 都是保存在 HashMap 中的,LinkedHashMap 还会将 Entry 放到链表的尾部。

    source code

    我们来看一下比较重要的伪代码(省略了很多类似 clear 之类的代码):

    public class LinkedHashMap<K,V>
      extends HashMap<K,V>
      implements Map<K,V> {
    
      /**
       * 重新定义 Node,增加了 before 和 after 用于维护双向链表。
       * next 用于维护 HashMap 各个桶中的 Entry 的连接顺序;
       * before 和 after 维护 entry 插入的先后顺序
       */
      static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
          LinkedHashMapEntry<K,V> before, after;
          LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
              super(hash, key, value, next);
          }
      }
    
      //双向链表的头结点和尾结点
      transient LinkedHashMapEntry<K,V> head;
      transient LinkedHashMapEntry<K,V> tail;
    
      //双向链表的迭代顺序,false 是按照插入顺序,true 是按照访问顺序
      final boolean accessOrder;
    
      // 将结点放到尾部
      private void linkNodeLast(LinkedHashMapEntry<K,V> p) {
          LinkedHashMapEntry<K,V> last = tail;
          tail = p;
          if (last == null)
              head = p;
          else {
              p.before = last;
              last.after = p;
          }
      }
    
      // 创建新结点的时候,将结点放到链表的尾部
      Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
          LinkedHashMapEntry<K,V> p =
              new LinkedHashMapEntry<K,V>(hash, key, value, e);
          linkNodeLast(p);
          return p;
      }
    
      void afterNodeInsertion(boolean evict) { // possibly remove eldest
          LinkedHashMapEntry<K,V> first;
          if (evict && (first = head) != null && removeEldestEntry(first)) {
              K key = first.key;
              removeNode(hash(key), key, null, false, true);
          }
      }
    
      // 调用 get 方法访问一个结点后,如果 accessOrder = true,则会调用该方法将该结点放到尾部
      // LRU Cache 就是利用的 LinkedHashMap 这点
      void afterNodeAccess(Node<K,V> e) { // move node to last
          LinkedHashMapEntry<K,V> last;
          if (accessOrder && (last = tail) != e) {
              LinkedHashMapEntry<K,V> p =
                  (LinkedHashMapEntry<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;
          }
      }
    
      /**
       * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
       * with the default initial capacity (16) and load factor (0.75).
       */
      public LinkedHashMap() {
          super();
          accessOrder = false;
      }
    
      /**
       * 主要看下这个构造函数
       *
       * @param  initialCapacity 初始容量
       * @param  loadFactor      加载因子
       * @param  accessOrder     顺序 - true 则按照访问顺序,false 按照插入顺序
       */
      public LinkedHashMap(int initialCapacity,
                           float loadFactor,
                           boolean accessOrder) {
          super(initialCapacity, loadFactor);
          this.accessOrder = accessOrder;
      }
    
      public V get(Object key) {
          Node<K,V> e;
          if ((e = getNode(hash(key), key)) == null)
              return null;
          if (accessOrder) // 调用 get 方法后,会将该结点放到链表尾部
              afterNodeAccess(e);
          return e.value;
      }
    
      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) {
              int mc = modCount;
              // Android-changed: Detect changes to modCount early.
              for (LinkedHashMapEntry<K,V> e = head; (e != null && modCount == mc); e = e.after)
                  action.accept(e.key);
          }
      }
    
      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) {
              int mc = modCount;
              // Android-changed: Detect changes to modCount early.
              for (LinkedHashMapEntry<K,V> e = head; (e != null && modCount == mc); e = e.after)
                  action.accept(e.value);
          }
      }
    
      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) {
              int mc = modCount;
              // Android-changed: Detect changes to modCount early.
              for (LinkedHashMapEntry<K,V> e = head; (e != null && mc == modCount); e = e.after)
                  action.accept(e);
          }
      }
    
      // Map overrides
    
      public void forEach(BiConsumer<? super K, ? super V> action) {
          if (action == null)
              throw new NullPointerException();
          int mc = modCount;
          // Android-changed: Detect changes to modCount early.
          for (LinkedHashMapEntry<K,V> e = head; modCount == mc && 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;
          // Android-changed: Detect changes to modCount early.
          for (LinkedHashMapEntry<K,V> e = head; modCount == mc && e != null; e = e.after)
              e.value = function.apply(e.key, e.value);
          if (modCount != mc)
              throw new ConcurrentModificationException();
      }
    
      // Iterators
    
      abstract class LinkedHashIterator {
          LinkedHashMapEntry<K,V> next;
          LinkedHashMapEntry<K,V> current;
          int expectedModCount;
    
          LinkedHashIterator() {
              next = head;
              expectedModCount = modCount;
              current = null;
          }
    
          public final boolean hasNext() {
              return next != null;
          }
    
          final LinkedHashMapEntry<K,V> nextNode() {
              LinkedHashMapEntry<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(); }
      }
    }