【深入学习java集合系列】LinkedHashMap的底层实现

来源:互联网 发布:网络有初心 编辑:程序博客网 时间:2024/05/16 04:43

最近写到LeetCode上的某一题LRUCache。可以采用LinkedHashMap实现,通过重写removeEldestEntry方法,即可实现。

    LinkedHashMap map;    public LRUCache(int capacity) {        map = new LinkedHashMap<Integer, Integer>(capacity, 0.75f, true) {            // 定义put后的移除规则,大于容量就删除eldest            protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {                return size() > capacity;            }        };    }    public int get(int key) {        return map.containsKey(key) ? (int)map.get(key) : -1 ;    }    public void put(int key, int value) {            map.put(key, value);    }

但是,这样实在太耍赖了,所以决定研究LinkedHashMap的底层到底是如何实现的?源码应该是最好的学习代码了。


1、继承关系

  1. public class LinkedHashMap<K,V>  
  2.     extends HashMap<K,V>  
  3.     implements Map<K,V>  

linkedhashmap继承hashMap,底层的主要存储结构是Hashmap的table。LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。

2、成员变量

  1. private transient Entry<K,V> header;
  2. private final boolean accessOrder;  

首先header是一个双向链表,具体体现在Entry结构中,我们知道hashmap的Entry是一个单向链表,只有一个next属性。

其次就是accessOrder,它决定了linkedhashmap中的元素在遍历的时候的输出顺序(插入顺序或者是访问顺序)。

3、Entry对象

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

4、构造函数

  1. //默认accessOrder为false  
  2. //调用HashMap构造函数  
  3. public LinkedHashMap()  
  4.     super();  
  5.     accessOrder false 
  6.  
  7.   
  8. //如果想实现LRU算法,参考这个构造函数  
  9. public LinkedHashMap(int initialCapacity, float loadFactor,  
  10.         boolean accessOrder)  
  11.     super(initialCapacity, loadFactor);  
  12.     this.accessOrder accessOrder;  
  13.  
  14.   
  15. //模板方法模式,HashMap构造函数里面的会调用init()方法  
  16. //初始化的时候map里没有任何Entry,让header.before header.after header  
  17. void init()  
  18.     header new Entry(-1nullnullnull);  
  19.     header.before header.after header;  
  20. }

5、存储数据

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

  1. //LinkedHashMap没有put(K key, value)方法,只重写了被put调用的addEntry方法  
  2. //1是HashMap里原有的逻辑,23是LinkedHashMap特有的  
  3. void addEntry(int hash, key, value, int bucketIndex)  
  4.     createEntry(hash, key, value, bucketIndex);  
  5.   
  6.     Entry eldest header.after;  
  7.     //3.如果有必要,移除LRU里面最老的Entry,否则判断是否该resize  
  8.     if (removeEldestEntry(eldest))  
  9.         removeEntryForKey(eldest.key);  
  10.     else  
  11.         if (size >= threshold)  
  12.             resize(2 table.length);  
  13.      
  14.  
  15.   
  16. void createEntry(int hash, key, value, int bucketIndex)  
  17.     //1.同HashMap一样:在Entry数组+next链表结构里面加入Entry  
  18.     HashMap.Entry old table[bucketIndex];  
  19.     Entry new Entry(hash, key, value, old);  
  20.     table[bucketIndex] e;  
  21.     //2.把新Entry也加到header链表结构里面去  
  22.     e.addBefore(header);  
  23.     size++;  
  24.  
  25.   
  26. //默认是false,我们可以重写此方法  
  27. protected boolean removeEldestEntry(Map.Entry eldest)  
  28.     return false 
  29.  
  30.   
  31. private static class Entry extends HashMap.Entry  
  32.     //链表插入元素四个步骤,对着图看  
  33.     private void addBefore(Entry existingEntry)  
  34.         after existingEntry;                //1  
  35.         before existingEntry.before;     //2  
  36.         before.after this                  //3  
  37.         after.before this                  //4  
  38.      
  39.         
  40.        
  41.         //如果走到resize,会调用这里重写的transfer  
  42. //HashMap里面的transfer是n m次运算,LinkedHashtable重写后是n m次运算  
  43. void transfer(HashMap.Entry[] newTable)  
  44.     int newCapacity newTable.length;  
  45.     //直接遍历header链表,HashMap里面是遍历Entry数组  
  46.     for (Entry header.after; != header; e.after)  
  47.         int index indexFor(e.hash, newCapacity);  
  48.         e.next newTable[index];  
  49.         newTable[index] e;  
  50.      
  51.   
6、读取数据

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

  1. //重写了get(Object key)方法  
  2. public get(Object key)  
  3.     //1.调用HashMap的getEntry方法得到e  
  4.     Entry (Entry) getEntry(key);  
  5.     if (e == null 
  6.         return null 
  7.     //2.LinkedHashMap牛B的地方  
  8.     e.recordAccess(this);  
  9.     return e.value;  
  10.  
  11.   
  12.        // 继承了HashMap.Entry  
  13. private static class Entry extends HashMap.Entry  
  14.     //1.此方法提供了LRU的实现  
  15.     //2.通过12两步,把最近使用的当前Entry移到header的before位置,而LinkedHashIterator遍历的方式是从header.after开始遍历,先得到最近使用的Entry  
  16.     //3.最近使用是什么意思:accessOrder为true时,get(Object key)方法会导致Entry最近使用;put(K key, value)/putForNullKey(value)只有是覆盖操作时会导致Entry最近使用。它们都会触发recordAccess方法从而导致Entry最近使用  
  17.     //4.总结LinkedHashMap迭代方式:accessOrder=false时,迭代出的数据按插入顺序;accessOrder=true时,迭代出的数据按LRU顺序+插入顺序  
  18.     //  HashMap迭代方式:横向数组 竖向next链表  
  19.     void recordAccess(HashMap m)  
  20.         LinkedHashMap lm (LinkedHashMap) m;  
  21.         //如果使用LRU算法  
  22.         if (lm.accessOrder)  
  23.             lm.modCount++;  
  24.             //1.从header链表里面移除当前Entry  
  25.             remove();  
  26.             //2.把当前Entry移到header的before位置  
  27.             addBefore(lm.header);  
  28.          
  29.      
  30.       
  31.     //让当前Entry从header链表消失  
  32.     private void remove()  
  33.         before.after after;  
  34.         after.before before;  
  35.      
  36.         

7、删除数据

  1.        // 继承了HashMap.Entry  
  2. private static class Entry extends HashMap.Entry  
  3.     //LinkedHashMap没有重写remove(Object key)方法,重写了被remove调用的recordRemoval方法  
  4.     //这个方法的设计也和精髓,也是模板方法模式  
  5.     //HahsMap remove(Object key)把数据从横向数组 竖向next链表里面移除之后(就已经完成工作了,所以HashMap里面recordRemoval是空的实现调用了此方法  
  6.     //但在LinkedHashMap里面,还需要移除header链表里面Entry的after和before关系  
  7.     void recordRemoval(HashMap m)  
  8.         remove();  
  9.      
  10.       
  11.     //让当前Entry从header链表消失  
  12.     private void remove()  
  13.         before.after after;  
  14.         after.before before;  
  15.      
  16.  
8、遍历数据

  1. private abstract class LinkedHashIterator implements Iterator  
  2.     //从header.after开始遍历  
  3.     Entry nextEntry header.after;  
  4.       
  5.     Entry nextEntry()  
  6.         if (modCount != expectedModCount)  
  7.             throw new ConcurrentModificationException();  
  8.         if (nextEntry == header)  
  9.             throw new NoSuchElementException();  
  10.   
  11.         Entry lastReturned nextEntry;  
  12.         nextEntry e.after;  
  13.         return e;  
  14.      
  15.  

九.总结

  1. LinkedHashMap继承HashMap,结构2里数据结构的变化交给HashMap就行了。
  2. 结构1里数据结构的变化就由LinkedHashMap里重写的方法去实现。
  3. 简言之:LinkedHashMap比HashMap多维护了一个链表。
 
阅读全文
0 0
原创粉丝点击