• java集合类分析-LinkedHashMap


    1. LinkedHashMap概述:

    LinkedHashMapHashMap的一个子类,它保留插入的顺序,如果需要输出的顺序和输入时的相同,那么就选用LinkedHashMap

       LinkedHashMapMap接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现提供所有可选的映射操作,并允许使用null值和null。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
       LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。
       注意,此实现不是同步的。如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。

    根据链表中元素的顺序可以分为:按插入顺序的链表,和按访问顺序(调用get方法)的链表。  

    默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。  可以重写removeEldestEntry方法返回true值指定插入元素时移除最老的元素。 

    2. LinkedHashMap的实现:

       对于LinkedHashMap而言,它继承与HashMap、底层使用哈希表与双向链表来保存所有元素。其基本操作与父类HashMap相似,它通过重写父类相关的方法,来实现自己的链接列表特性。下面我们来分析LinkedHashMap的源代码:

    类结构:

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

     1) 成员变量:

       LinkedHashMap采用的hash算法和HashMap相同,但是它重新定义了数组中保存的元素Entry,该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而在哈希表的基础上又构成了双向链接列表。看源代码:

     

    //true表示按照访问顺序迭代,false时表示按照插入顺序  
     private final boolean accessOrder;  
    /** 
     * 双向链表的表头元素。 
     */  
    private transient Entry<K,V> header;  
      
    /** 
     * LinkedHashMap的Entry元素。 
     * 继承HashMap的Entry元素,又保存了其上一个元素before和下一个元素after的引用。 
     */  
    private static class Entry<K,V> extends HashMap.Entry<K,V> {  
        Entry<K,V> before, after;  
        ……  
    }  
    HashMap.Entry:
    static class Entry<K,V> implements Map.Entry<K,V> {  
            final K key;  
            V value;  
            Entry<K,V> next;  
            final int hash;  
      
            Entry(int h, K k, V v, Entry<K,V> n) {  
                value = v;  
                next = n;  
                key = k;  
                hash = h;  
            }  
    }  
    

     

      

    2) 初始化:

       通过源代码可以看出,在LinkedHashMap的构造方法中,实际调用了父类HashMap的相关构造方法来构造一个底层存放的table数组。如:

     

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

    HashMap中的相关构造方法:

     public HashMap(int initialCapacity, float loadFactor) {  
    
         if (initialCapacity < 0)  
    
             throw new IllegalArgumentException("Illegal initial capacity: " +  
    
                                                initialCapacity);  
    
        if (initialCapacity > MAXIMUM_CAPACITY)  
    
           initialCapacity = MAXIMUM_CAPACITY;  
    
        if (loadFactor <= 0 || Float.isNaN(loadFactor))  
    
            throw new IllegalArgumentException("Illegal load factor: " +  
    
                                               loadFactor);  
    
         // Find a power of 2 >= initialCapacity  
    
         int capacity = 1;  
    
         while (capacity < initialCapacity)  
    
            capacity <<= 1;  
     
         this.loadFactor = loadFactor;  
         threshold = (int)(capacity * loadFactor);  
    
         table = new Entry[capacity];  
    
     
    
         init();  
    
    } 
    

      

    我们已经知道LinkedHashMapEntry元素继承HashMapEntry,提供了双向链表的功能。在上述HashMap的构造器中,最后会调用init()方法,进行相关的初始化,这个方法在HashMap的实现中并无意义,只是提供给子类实现相关的初始化调用。
       LinkedHashMap重写了init()方法,在调用父类的构造方法完成构造后,进一步实现了对其元素Entry的初始化操作。

     

    1 void init() {  

     

    2     header = new Entry<K,V>(-1, null, null, null);  

     

    3     header.before = header.after = header;  

     

    4 }  

        3) 存储:

       LinkedHashMap并未重写父类HashMapput方法,而是重写了父类HashMapput方法调用的子方法void recordAccess(HashMap m)   void addEntry(int hash, K key, V value, int bucketIndex) void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的双向链接列表的实现。

    HashMap.put:

     

    1 public V put(K key, V value) {  

    2         if (key == null)  

    3             return putForNullKey(value);  

    4         int hash = hash(key.hashCode());  

    5         int i = indexFor(hash, table.length);  

    6         for (Entry<K,V> e = table[i]; e != null; e = e.next) {  

    7             Object k;  

    8             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  

    9                 V oldValue = e.value;  

    10                 e.value = value;  

    11                 e.recordAccess(this);  

    12                 return oldValue;  

    13             }  

    14         }  

    15   

    16         modCount++;  

    17         addEntry(hash, key, value, i);  

    18         return null;  

    19     }  

     重写方法:

    20 void recordAccess(HashMap<K,V> m) {  

    21             LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;  

    22             if (lm.accessOrder) {  

    23                 lm.modCount++;  

    24                 remove();  

    25                 addBefore(lm.header);  

    26             }  

    27         }  

     

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

    29     // 调用create方法,将新元素以双向链表的的形式加入到映射中。  

    30     createEntry(hash, key, value, bucketIndex);  

    31   

    32     // 删除最近最少使用元素的策略定义  

    33     Entry<K,V> eldest = header.after;  

    34     if (removeEldestEntry(eldest)) {  

    35         removeEntryForKey(eldest.key);  

    36     } else {  

    37         if (size >= threshold)  

    38             resize(2 * table.length);  

    39     }  

    40 }  

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

    42     HashMap.Entry<K,V> old = table[bucketIndex];  

    43     Entry<K,V> e = new Entry<K,V>(hash, key, value, old);  

    44     table[bucketIndex] = e;  

    45     // 调用元素的addBrefore方法,将元素加入到哈希、双向链接列表。  

    46     e.addBefore(header);  

    47     size++;  

    48 }  

    49 private void addBefore(Entry<K,V> existingEntry) {  

    50     after  = existingEntry;  

    51     before = existingEntry.before;  

    52     before.after = this;  

    53     after.before = this;  

    54 }  

       

    4) 读取:

       LinkedHashMap重写了父类HashMapget方法,实际在调用父类getEntry()方法取得查找的元素后,再判断当排序模式accessOrdertrue时,记录访问顺序,将最新访问的元素添加到双向链表的表头,并从原来的位置删除。由于的链表的增加、删除操作是常量级的,故并不会带来性能的损失。

    HashMap.containsValue:

    55 public boolean containsValue(Object value) {  
    
    56     if (value == null)  
    
    57             return containsNullValue();  
    
    58   
    
    59     Entry[] tab = table;  
    
    60         for (int i = 0; i < tab.length ; i++)  
    
    61             for (Entry e = tab[i] ; e != null ; e = e.next)  
    
    62                 if (value.equals(e.value))  
    
    63                     return true;  
    
    64     return false;  
    
    65     }  
    
     
    
    66  /*查找Map中是否包含给定的value,还是考虑到,LinkedHashMap拥有的双链表,在这里Override是为了提高迭代的效率。 
    
    67  */  
    
    68 public boolean containsValue(Object value) {  
    
    69         // Overridden to take advantage of faster iterator  
    
    70         if (value==null) {  
    
    71             for (Entry e = header.after; e != header; e = e.after)  
    
    72                 if (e.value==null)  
    
    73                     return true;  
    
    74         } else {  
    
    75             for (Entry e = header.after; e != header; e = e.after)  
    
    76                 if (value.equals(e.value))  
    
    77                     return true;  
    
    78         }  
    
    79         return false;  
    
    80     }  
    
     
    
     
    
    81 /*该transfer()是HashMap中的实现:遍历整个表的各个桶位,然后对桶进行遍历得到每一个Entry,重新hash到newTable中, 
    
    82  //放在这里是为了和下面LinkedHashMap重写该法的比较, 
    
    83  void transfer(Entry[] newTable) { 
    
    84         Entry[] src = table; 
    
    85         int newCapacity = newTable.length; 
    
    86         for (int j = 0; j < src.length; j++) { 
    
    87             Entry<K,V> e = src[j]; 
    
    88             if (e != null) { 
    
    89                 src[j] = null; 
    
    90                 do { 
    
    91                     Entry<K,V> next = e.next; 
    
    92                     int i = indexFor(e.hash, newCapacity); 
    
    93                     e.next = newTable[i]; 
    
    94                     newTable[i] = e; 
    
    95                     e = next; 
    
    96                 } while (e != null); 
    
    97             } 
    
    98         } 
    
    99     } 
    
    100  */  
    
    101   
    
    102  /** 
    
    103  *transfer()方法是其父类HashMap调用resize()的时候调用的方法,它的作用是表扩容后,把旧表中的key重新hash到新的表中。 
    
    104  *这里从写了父类HashMap中的该方法,是因为考虑到,LinkedHashMap拥有的双链表,在这里Override是为了提高迭代的效率。 
    
    105  */  
    
    106  void transfer(HashMap.Entry[] newTable) {  
    
    107    int newCapacity = newTable.length;  
    
    108    for (Entry<K, V> e = header.after; e != header; e = e.after) {  
    
    109      int index = indexFor(e.hash, newCapacity);  
    
    110      e.next = newTable[index];  
    
    111      newTable[index] = e;  
    
    112    }  
    
    113  }  
    
    
    114 public V get(Object key) {  
    
    115     // 调用父类HashMap的getEntry()方法,取得要查找的元素。  
    
    116     Entry<K,V> e = (Entry<K,V>)getEntry(key);  
    
    117     if (e == null)  
    
    118         return null;  
    
    119     // 记录访问顺序。  
    
    120     e.recordAccess(this);  
    
    121     return e.value;  
    
    122 }  
    
    123 void recordAccess(HashMap<K,V> m) {  
    
    124     LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;  
    
    125     // 如果定义了LinkedHashMap的迭代顺序为访问顺序,  
    
    126     // 则删除以前位置上的元素,并将最新访问的元素添加到链表表头。  
    
    127     if (lm.accessOrder) {  
    
    128         lm.modCount++;  
    
    129         remove();  
    
    130         addBefore(lm.header);  
    
    131     }  
    
    132 }  
    
    
    133 /** 
    
    134          * Removes this entry from the linked list. 
    
    135          */  
    
    136         private void remove() {  
    
    137             before.after = after;  
    
    138             after.before = before;  
    
    139         }  
    
    
    140 /**clear链表,设置header为初始状态*/  
    
    141 public void clear() {  
    
    142  super.clear();  
    
    143  header.before = header.after = header;  
    
    144 }  
    

      

     

     

        5) 排序模式:

       LinkedHashMap定义了排序模式accessOrder,该属性为boolean型变量,对于访问顺序,为true;对于插入顺序,则为false

    145 private final boolean accessOrder;  

     一般情况下,不必指定排序模式,其迭代顺序即为默认为插入顺序。看LinkedHashMap的构造方法,如:

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

      

        这些构造方法都会默认指定排序模式为插入顺序。如果你想构造一个LinkedHashMap,并打算按从近期访问最少到近期访问最多的顺序(即访问顺序)来保存元素,那么请使用下面的构造方法构造LinkedHashMap

    150 public LinkedHashMap(int initialCapacity,  
    
    151          float loadFactor,  
    
    152                      boolean accessOrder) {  
    
    153     super(initialCapacity, loadFactor);  
    
    154     this.accessOrder = accessOrder;  
    
    155 }  
    

      

        该哈希映射的迭代顺序就是最后访问其条目的顺序,这种映射很适合构建LRU缓存。LinkedHashMap提供了removeEldestEntry(Map.Entry<K,V> eldest)方法。该方法可以提供在每次添加新条目时移除最旧条目的实现程序,默认返回false,这样,此映射的行为将类似于正常映射,即永远不能移除最旧的元素。

     

    当有新元素加入Map的时候会调用EntryaddEntry方法,会调用removeEldestEntry方法,这里就是实现LRU元素过期机制的地方,默认的情况下removeEldestEntry方法只返回false表示元素永远不过期。

    156   /** 
    
    157     * This override alters behavior of superclass put method. It causes newly 
    
    158     * allocated entry to get inserted at the end of the linked list and 
    
    159     * removes the eldest entry if appropriate. 
    
    160     */  
    
    161    void addEntry(int hash, K key, V value, int bucketIndex) {  
    
    162        createEntry(hash, key, value, bucketIndex);  
    
    163   
    
    164        // Remove eldest entry if instructed, else grow capacity if appropriate  
    
    165        Entry<K,V> eldest = header.after;  
    
    166        if (removeEldestEntry(eldest)) {  
    
    167            removeEntryForKey(eldest.key);  
    
    168        } else {  
    
    169            if (size >= threshold)   
    
    170                resize(2 * table.length);  
    
    171        }  
    
    172    }  
    
    173   
    
    174    /** 
    
    175     * This override differs from addEntry in that it doesn't resize the 
    
    176     * table or remove the eldest entry. 
    
    177     */  
    
    178    void createEntry(int hash, K key, V value, int bucketIndex) {  
    
    179        HashMap.Entry<K,V> old = table[bucketIndex];  
    
    180 Entry<K,V> e = new Entry<K,V>(hash, key, value, old);  
    
    181        table[bucketIndex] = e;  
    
    182        e.addBefore(header);  
    
    183        size++;  
    
    184    }  
    
    185   
    
    186    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {  
    
    187        return false;  
    
    188    }  
    

      

    此方法通常不以任何方式修改映射,相反允许映射在其返回值的指引下进行自我修改。如果用此映射构建LRU缓存,则非常方便,它允许映射通过删除旧条目来减少内存损耗。

       例如:重写此方法,维持此映射只保存100个条目的稳定状态,在每次添加新条目时删除最旧的条目。

    189 private static final int MAX_ENTRIES = 100;  

    190 protected boolean removeEldestEntry(Map.Entry eldest) {  

    191     return size() > MAX_ENTRIES;  

    192 }  

     来源:http://zhangshixi.iteye.com/blog/673789

    参考:http://hi.baidu.com/yao1111yao/blog/item/3043e2f5657191f07709d7bb.html

    部分修改。

    使用LinkedHashMap构建LRUCache

    http://tomyz0223.iteye.com/blog/1035686

    基于LinkedHashMap实现LRU缓存调度算法原理及应用

    http://woming66.iteye.com/blog/1284326

    其实LinkedHashMap几乎和HashMap一样,不同的是它定义了一个Entry<K,V> header,这个header不是放在Table里,它是额外独立出来的。LinkedHashMap通过继承hashMap中的Entry<K,V>,并添加两个属性Entry<K,V>  before,after,header结合起来组成一个双向链表,来实现按插入顺序或访问顺序排序。

     

     

     

  • 相关阅读:
    ZOJ 3765 Lights (zju March I)伸展树Splay
    UVA 11922 伸展树Splay 第一题
    UVALive 4794 Sharing Chocolate DP
    ZOJ 3757 Alice and Bod 模拟
    UVALive 3983 捡垃圾的机器人 DP
    UVA 10891 SUM游戏 DP
    poj 1328 Radar Installatio【贪心】
    poj 3264 Balanced Lineup【RMQ-ST查询区间最大最小值之差 +模板应用】
    【转】RMQ-ST算法详解
    poj 3083 Children of the Candy Corn 【条件约束dfs搜索 + bfs搜索】【复习搜索题目一定要看这道题目】
  • 原文地址:https://www.cnblogs.com/prctice/p/5488355.html
Copyright © 2020-2023  润新知