LinkedHashMap解读

一.LinkedHashMap的存储结构

LinkedHashMap存储结构

  1. LinkedHashMap是继承HashMap,也就继承了HashMap的结构,也就是图中的结构2,在下文中我用"Entry数组+next链表"来描述。而LinkedHashMap有其自己的变量header,也就是图中的结构1,下文中我用"header链表"来描述。
  2. 结构1中的Entry和结构2中的Entry本是同一个,结构1中应该就只有一个header,它指向的是结构2中的e1 e2,但这样会使结构图难画。为了说明问题的方便,我把结构2里的e1 e2在结构1中多画一个。

 

二.LinkedHashMap成员变量

Java代码   收藏代码
  1. // LinkedHashMap维护了一个链表,header是链表头。此链表不同于HashMap里面的那个next链表  
  2. private transient Entry<K, V> header;  
  3.   
  4. // LRU:Least Recently Used最近最少使用算法  
  5. // accessOrder决定是否使用此算法,accessOrder=true使用  
  6. private final boolean accessOrder;  

 

三.LinkedHashMap里的Entry对象

Java代码   收藏代码
  1.       // 继承了HashMap.Entry,其他几个方法边用边分析  
  2. rivate static class Entry<K, V> extends HashMap.Entry<K, V> {  
  3. // 增加了两个属性,每个Entry有before Entry和after Entry,就构成了一个链表  
  4. Entry<K, V> before, after;  
  5.   
  6. Entry(int hash, K key, V value, HashMap.Entry<K, V> next) {  
  7.     super(hash, key, value, next);  
  8. }  
  9.   
  10. private void addBefore(Entry<K, V> existingEntry) {  
  11.     .....  
  12. }  
  13.   
  14. void recordAccess(HashMap<K, V> m) {  
  15.     .....  
  16. }  
  17.   
  18. void recordRemoval(HashMap<K, V> m) {  
  19.     .....  
  20. }  
  21.   
  22. private void remove() {  
  23.     .....  
  24. }  
 

四.构造函数

Java代码   收藏代码
  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<K, V>(-1nullnullnull);  
  19.     header.before = header.after = header;  
  20. }  
 

五.存数据

Java代码   收藏代码
  1. //LinkedHashMap没有put(K key, V value)方法,只重写了被put调用的addEntry方法  
  2. //1是HashMap里原有的逻辑,23是LinkedHashMap特有的  
  3. void addEntry(int hash, K key, V value, int bucketIndex) {  
  4.     createEntry(hash, key, value, bucketIndex);  
  5.   
  6.     Entry<K, V> 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, K key, V value, int bucketIndex) {  
  17.     //1.同HashMap一样:在Entry数组+next链表结构里面加入Entry  
  18.     HashMap.Entry<K, V> old = table[bucketIndex];  
  19.     Entry<K, V> e = new Entry<K, V>(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<K, V> eldest) {  
  28.     return false;  
  29. }  
  30.   
  31. private static class Entry<K, V> extends HashMap.Entry<K, V> {  
  32.     //链表插入元素四个步骤,对着图看  
  33.     private void addBefore(Entry<K, V> 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<K, V> e = header.after; e != header; e = e.after) {  
  47.         int index = indexFor(e.hash, newCapacity);  
  48.         e.next = newTable[index];  
  49.         newTable[index] = e;  
  50.     }  
  51.  }  

     下面三个图是初始化LinkedHashMap------->添加Entry e1------>添加Entry e2时,LinkedHashMap结构的变化。

LinkedHashMap存储结构

LinkedHashMap存储结构

LinkedHashMap存储结构

 

六.取数据

Java代码   收藏代码
  1. //重写了get(Object key)方法  
  2. public V get(Object key) {  
  3.     //1.调用HashMap的getEntry方法得到e  
  4.     Entry<K, V> e = (Entry<K, V>) 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<K, V> extends HashMap.Entry<K, V> {  
  14.     //1.此方法提供了LRU的实现  
  15.     //2.通过12两步,把最近使用的当前Entry移到header的before位置,而LinkedHashIterator遍历的方式是从header.after开始遍历,先得到最近使用的Entry  
  16.     //3.最近使用是什么意思:accessOrder为true时,get(Object key)方法会导致Entry最近使用;put(K key, V value)/putForNullKey(value)只有是覆盖操作时会导致Entry最近使用。它们都会触发recordAccess方法从而导致Entry最近使用  
  17.     //4.总结LinkedHashMap迭代方式:accessOrder=false时,迭代出的数据按插入顺序;accessOrder=true时,迭代出的数据按LRU顺序+插入顺序  
  18.     //  HashMap迭代方式:横向数组 * 竖向next链表  
  19.     void recordAccess(HashMap<K, V> m) {  
  20.         LinkedHashMap<K, V> lm = (LinkedHashMap<K, V>) 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.        }  
 

七.删数据

Java代码   收藏代码
  1.        // 继承了HashMap.Entry  
  2. private static class Entry<K, V> extends HashMap.Entry<K, V> {  
  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<K, V> m) {  
  8.         remove();  
  9.     }  
  10.       
  11.     //让当前Entry从header链表消失  
  12.     private void remove() {  
  13.         before.after = after;  
  14.         after.before = before;  
  15.     }  
  16. }  
 

八.LinkedHashMap EntrySet遍历

Java代码   收藏代码
  1.        private abstract class LinkedHashIterator<T> implements Iterator<T> {  
  2.     //从header.after开始遍历  
  3.     Entry<K, V> nextEntry = header.after;  
  4.       
  5.     Entry<K, V> nextEntry() {  
  6.         if (modCount != expectedModCount)  
  7.             throw new ConcurrentModificationException();  
  8.         if (nextEntry == header)  
  9.             throw new NoSuchElementException();  
  10.   
  11.         Entry<K, V> e = lastReturned = nextEntry;  
  12.         nextEntry = e.after;  
  13.         return e;  
  14.     }  
  15. }  
  1. 上图中,遍历的结果是先e1然后e2。
  2. accessOrder为true时,get(e1.key)或者put(e1.key, value)一下,则结构1变成e2------e1------header,遍历的结果就是先e2然后e1。

 

九.总结

  1. LinkedHashMap继承HashMap,结构2里数据结构的变化交给HashMap就行了。
  2. 结构1里数据结构的变化就由LinkedHashMap里重写的方法去实现。
  3. 简言之:LinkedHashMap比HashMap多维护了一个链表。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值