zoukankan      html  css  js  c++  java
  • 【Java集合系列六】LinkedHashMap解析

    2017-08-14 16:30:10

    1、简介

    LinkedHashMap继承自HashMap,能保证迭代顺序,支持其他Map可选的操作。采用双向链表存储元素,默认的迭代序是插入序。重复插入一个已经存在的key不影响此顺序。如果accessOrder参数被使用且置为true,迭代序使用访问序,访问序受put、get、putAll等方法的影响,但不受集合视图操作的影响(其实HashMap中好像并没有什么视图操作,不像List有subList方法)。LinkedHashMap不是线程安全的。

    2、与HashMap相比特殊点

    之前提到了,HashMap不保持顺序,但是LinkedHashMap能保证迭代序。同时还支持两种遍历顺序:插入序和访问序。之所以能实现这些功能和效果,是因为LinkedHashMap重载了LinkedEntry,实现了双向链表,所以插入时,同时插入到table数组和双向链表header,如果是访问序,则在get、putAll等方法操作时,会将操作过的元素链接到表尾,保证链表尾部永远是最近使用过元素。

    总结就是:LinkedHashMap有两套元素存储机制:数组table和header,所有区别于HashMap的特点都是通过双向链表header实现的。

    3、LinkedEntry

    LinkedEntry继承自HashMapEntry,新增了2个元素:nxt和prv来实现双向链表,代码如下:

     1 /**
     2      * LinkedEntry adds nxt/prv double-links to plain HashMapEntry.
     3      */
     4     static class LinkedEntry<K, V> extends HashMapEntry<K, V> {
     5         LinkedEntry<K, V> nxt;
     6         LinkedEntry<K, V> prv;
     7 
     8         /** Create the header entry */
     9         LinkedEntry() {
    10             super(null, null, 0, null);
    11             nxt = prv = this;
    12         }
    13 
    14         /** Create a normal entry */
    15         LinkedEntry(K key, V value, int hash, HashMapEntry<K, V> next,
    16                     LinkedEntry<K, V> nxt, LinkedEntry<K, V> prv) {
    17             super(key, value, hash, next);
    18             this.nxt = nxt;
    19             this.prv = prv;
    20         }
    21     }

    4、put操作

    LinkedHashMap没有重写put方法,而是重写了put方法调用的addNewEntry方法,该方法执行真正插入一个元素的操作。插入元素时,同时插入到table数组和header双向链表,代码如下:

     1 @Override void addNewEntry(K key, V value, int hash, int index) {
     2         LinkedEntry<K, V> header = this.header;
     3 
     4         // 移除最久没使用过的元素,removeEldestEntry方法默认返回false,适合子类重写
     5         LinkedEntry<K, V> eldest = header.nxt;
     6         if (eldest != header && removeEldestEntry(eldest)) {
     7             remove(eldest.key);
     8         }
     9 
    10         // Create new entry, link it on to list, and put it into table
    11         // 1、将元素查到链表尾部;
    12         // 2、将元素插入到table数组中;
    13         LinkedEntry<K, V> oldTail = header.prv;
    14         LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(
    15                 key, value, hash, table[index], header, oldTail);//newTail.prv = oldTail, newTail.nxt = header,其实就是在Tail元素和Header元素之间插入
    16         table[index] = oldTail.nxt = header.prv = newTail; //1、前一行代码只处理了部分双向链表插入操作,这里继续处理,oldTail.nxt = newTail, header.prv = newTail;
    17                                                            //2、插入table[index];
    18     }
    19 
    20     @Override void addNewEntryForNullKey(V value) {
    21         LinkedEntry<K, V> header = this.header;
    22 
    23         // 移除最久没使用过的元素,removeEldestEntry方法默认返回false,适合子类重写
    24         LinkedEntry<K, V> eldest = header.nxt;
    25         if (eldest != header && removeEldestEntry(eldest)) {
    26             remove(eldest.key);
    27         }
    28 
    29         //与addNewEntry方法类似,只是没有插入数组的操作
    30         LinkedEntry<K, V> oldTail = header.prv;
    31         LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(
    32                 null, value, 0, null, header, oldTail);
    33         entryForNullKey = oldTail.nxt = header.prv = newTail;
    34     }

    5、get操作

     1     /**
     2      * Relinks the given entry to the tail of the list. Under access ordering,
     3      * this method is invoked whenever the value of a  pre-existing entry is
     4      * read by Map.get or modified by Map.put.
     5      */
     6     private void makeTail(LinkedEntry<K, V> e) {
     7         // 将元素e从当前位置移除
     8         // Unlink e
     9         e.prv.nxt = e.nxt;
    10         e.nxt.prv = e.prv;
    11 
    12         // 连接到链表尾部
    13         // Relink e as tail
    14         LinkedEntry<K, V> header = this.header;
    15         LinkedEntry<K, V> oldTail = header.prv;
    16         e.nxt = header;
    17         e.prv = oldTail;
    18         oldTail.nxt = header.prv = e;
    19         modCount++;
    20     }
    21 
    22     @Override public V get(Object key) {
    23         /*
    24          * This method is overridden to eliminate the need for a polymorphic
    25          * invocation in superclass at the expense of code duplication.
    26          */
    27         if (key == null) {
    28             HashMapEntry<K, V> e = entryForNullKey;
    29             if (e == null)
    30                 return null;
    31             if (accessOrder) //如果是访问序,将当前元素移到链表尾部(保证最近使用的元素在尾部)
    32                 makeTail((LinkedEntry<K, V>) e);
    33             return e.value;
    34         }
    35 
    36         // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590).
    37         // 这里的遍历操作与HashMap的类似,唯一的区别是:如果是访问序,则将该元素移到链表尾部
    38         int hash = secondaryHash(key);
    39         HashMapEntry<K, V>[] tab = table;
    40         for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
    41              e != null; e = e.next) {
    42             K eKey = e.key;
    43             if (eKey == key || (e.hash == hash && key.equals(eKey))) {
    44                 if (accessOrder)
    45                     makeTail((LinkedEntry<K, V>) e);
    46                 return e.value;
    47             }
    48         }
    49         return null;
    50     }

    6、preModify操作

    在HashMap的put、putValueForNullKey方法用到了preModify方法,作用就是保证访问序,代码如下:

    1     @Override void preModify(HashMapEntry<K, V> e) {
    2         if (accessOrder) {
    3             makeTail((LinkedEntry<K, V>) e);
    4         }
    5     }

    7、remove操作

    LinkedHashMap方法没有重写remove方法,但是重写了postRemove方法,该方法在HashMap的remove方法中有调用,如下:

    1     //作用:从双向链表中移除元素e
    2     @Override void postRemove(HashMapEntry<K, V> e) {
    3         LinkedEntry<K, V> le = (LinkedEntry<K, V>) e;
    4         le.prv.nxt = le.nxt;
    5         le.nxt.prv = le.prv;
    6         le.nxt = le.prv = null; // Help the GC (for performance)
    7     }

    8、Iterator体系

    LinkedHashMap保证迭代顺序,支持插入序和访问序,那么它的Iterator是怎么实现的?体系与HashMap类似,实现了最顶层的LinkedHashIterator,如下:

     1 private abstract class LinkedHashIterator<T> implements Iterator<T> {
     2         // 1、前面提到过,header只是一个虚拟元素,真正的表头元素是header.nxt,表尾元素是header.prv,所以遍历的时候从表头开始;
     3         // 2、nextEntry很简单,直接取nxt即可;
     4         LinkedEntry<K, V> next = header.nxt;
     5         LinkedEntry<K, V> lastReturned = null;
     6         int expectedModCount = modCount;
     7 
     8         public final boolean hasNext() {
     9             return next != header;
    10         }
    11 
    12         final LinkedEntry<K, V> nextEntry() {
    13             if (modCount != expectedModCount)
    14                 throw new ConcurrentModificationException();
    15             LinkedEntry<K, V> e = next;
    16             if (e == header)
    17                 throw new NoSuchElementException();
    18             next = e.nxt;
    19             return lastReturned = e;
    20         }
    21 
    22         // remove操作使用HashMap的remove方法,LinkedHashMap重写了postRemove方法
    23         public final void remove() {
    24             if (modCount != expectedModCount)
    25                 throw new ConcurrentModificationException();
    26             if (lastReturned == null)
    27                 throw new IllegalStateException();
    28             LinkedHashMap.this.remove(lastReturned.key);
    29             lastReturned = null;
    30             expectedModCount = modCount;
    31         }
    32     }

    至于其他的,就不用细说了,和HashMap类似。

  • 相关阅读:
    idea 编程字体推荐
    推荐!国外程序员整理的系统管理员资源大全
    jquery阻止事件冒泡的3种方式
    web前端打印总结
    前端打印插件
    object实现小老鼠交互
    前端性能优化(DOM篇)
    输入框获得焦点时外边框颜色改变
    webstorm配置scss自动编译路径
    微信开发测试号配置
  • 原文地址:https://www.cnblogs.com/wlrhnh/p/7359873.html
Copyright © 2011-2022 走看看