现在的位置: 首页 > 编程语言 > 正文

JDK源代码分析聚集篇——-LinkedHashMap(排队不能光图快却不讲次序啊!总有个先来后到吧!)

2019年06月12日 编程语言 ⁄ 共 6378字 ⁄ 字号 评论关闭
在上一节的分析中,我们已经对HashMap的内部实现机制有了一定的了解,我们感受到了快速定位对我们效率带来的好处和益处,但是我们不仅在嘀咕,效率是提高了,但是我却发现次序没有,除了能够预知"NULL"键的次序以外(傻瓜都知道在哪),其他的元素的次序我都是不感知的,如何是好,排队总得有个先来后到吧,别着急,我们下面来看一下LinkedHashMap这个类,他会帮我们解决问题的!
  1. package org.corey.demo;
  2. import java.util.HashMap;
  3. import java.util.Iterator;
  4. import java.util.LinkedHashMap;
  5. import java.util.Map;
  6. public class Test {
  7.     /**
  8.      * @param args
  9.      */
  10.     public static void main(String[] args) {
  11.         Map map = new LinkedHashMap(4);
  12.         map.put("1""corey");
  13.         map.put("2""syna");
  14.         map.put("3""bobo"); 
  15.         
  16.         Iterator it=map.keySet().iterator();
  17.         System.out.println("-------------------------linked hash map----------------------");
  18.         while(it.hasNext()){
  19.             String key=(String)it.next();
  20.             System.out.println(key+":"+map.get(key));
  21.         }
  22.         
  23.         Map map2 = new HashMap(4);
  24.         map2.put("1""corey");
  25.         map2.put("2""syna");
  26.         map2.put("3""bobo"); 
  27.         
  28.         Iterator it2=map2.keySet().iterator();
  29.         System.out.println("-------------------------hash map----------------------");
  30.         while(it2.hasNext()){
  31.             String key=(String)it2.next();
  32.             System.out.println(key+":"+map2.get(key));
  33.         }
  34.     }
  35. }

console:

-------------------------linked hash map----------------------
1:corey
2:syna
3:bobo
-------------------------hash map----------------------
2:syna
1:corey
3:bobo

从而可见,我们的问题得到了解决;

现在我们从LinkEdHashMap代码内部来详细的分析其实现原理;

  1. private transient Entry<K,V> header;

在LinkedHashMap中含有一个Entry的属性;注意,这个Entry不是上一次我们在HashMap中见到的单向Entry,他被扩充了:

  1.   private static class Entry<K,V> extends HashMap.Entry<K,V> {
  2.         //他有两个指针分别指向了before和after的Entry
  3.         //请注意,不要把next和他们混淆了,next的用处还是和HashMap中一//样;
  4.         Entry<K,V> before, after;
  5.     Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
  6.             super(hash, key, value, next);
  7.         }
  8.      
  9.         private void remove() {
  10.             before.after = after;
  11.             after.before = before;
  12.         }
  13.      、
  14.         //在当前节点前面插入节点;
  15.         private void addBefore(Entry<K,V> existingEntry) {
  16.             after  = existingEntry;
  17.             before = existingEntry.before;
  18.             before.after = this;
  19.             after.before = this;
  20.         }
  21.      
  22.         void recordAccess(HashMap<K,V> m) {
  23.             LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
  24.             if (lm.accessOrder) {
  25.                 lm.modCount++;
  26.                 remove();
  27.                 addBefore(lm.header);
  28.             }
  29.         }
  30.         void recordRemoval(HashMap<K,V> m) {
  31.             remove();
  32.         }
  33.     }

因为LinkedHashMap是继承的HashMap,那么他们的增删改的操作基本一致,只是利用了模板模式具有了不同的实现;
我们来看一下put方法吧;
下面是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.         modCount++;
  16.         addEntry(hash, key, value, i);
  17.         return null;
  18.     }
  1.  void addEntry(int hash, K key, V value, int bucketIndex) {
  2.     Entry<K,V> e = table[bucketIndex];
  3.         table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
  4.         if (size++ >= threshold)
  5.             resize(2 * table.length);
  6.     }

在上一节中我们已经详细的分析过了,在LinkedHashMap中被重载的方法是addEntry;

  1. void addEntry(int hash, K key, V value, int bucketIndex) {
  2.         createEntry(hash, key, value, bucketIndex);
  3.        
  4.         Entry<K,V> eldest = header.after;
  5.         if (removeEldestEntry(eldest)) {
  6.             removeEntryForKey(eldest.key);
  7.         } else {
  8.             if (size >= threshold)
  9.                 resize(2 * table.length);
  10.         }
  11.     }
  1.     void createEntry(int hash, K key, V value, int bucketIndex) {
  2.         //和以前一样,首先操作了table序列的链表枝条;
  3.         HashMap.Entry<K,V> old = table[bucketIndex];
  4.     Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
  5.         table[bucketIndex] = e;
  6.         //关键在于这一步做了什么;我们知道他在header之前插入了Entry;
  7.         e.addBefore(header);
  8.         size++;
  9.     }

Header元素在构造的时候被初始化,记得吗,这个方法是HashMap在构造函数中利用模板模式传下来的

  1.     void init() {
  2.         header = new Entry<K,V>(-1, nullnullnull);
  3.         header.before = header.after = header;
  4.     }

开始的时候Header头尾都是指向自身的;

第二步的时候就是被e.addBefore(header);

  1.  private void addBefore(Entry<K,V> existingEntry) {
  2.             after  = existingEntry;
  3.             before = existingEntry.before;
  4.             before.after = this;
  5.             after.before = this;
  6.         }


如果再put那么就是:


我们再来看一看他的keySet是如何得到的:

重载了newKeyIterator() 的方法:  

  1. Iterator<K> newKeyIterator()   { return new KeyIterator();   }

我们看一下迭代器是如何实现的:

  1.  private abstract class LinkedHashIterator<T> implements Iterator<T> {
  2.         //从开始的图3我们可见,第一次加入的节点是位于header.after的位置;
  3.     Entry<K,V> nextEntry    = header.after;
  4.        
  5.     Entry<K,V> lastReturned = null;
  6.     int expectedModCount = modCount;
  7.         
  8.         //nextnEntry不是header就不是末尾
  9.     public boolean hasNext() {         
  10.             return nextEntry != header;
  11.     }
  12.     public void remove() {
  13.         if (lastReturned == null)
  14.         throw new IllegalStateException();
  15.         if (modCount != expectedModCount)
  16.         throw new ConcurrentModificationException();
  17.             LinkedHashMap.this.remove(lastReturned.key);
  18.             lastReturned = null;
  19.             expectedModCount = modCount;
  20.     }
  21.     Entry<K,V> nextEntry() {
  22.         if (modCount != expectedModCount)
  23.         throw new ConcurrentModificationException();
  24.             if (nextEntry == header)
  25.                 throw new NoSuchElementException();
  26.             //指针沿着after往下走
  27.             Entry<K,V> e = lastReturned = nextEntry;
  28.             //预先把nextEntry移动到了下一位
  29.             nextEntry = e.after;
  30.             return e;
  31.     }
  32.     }

把next方法抽象出去是因为values和keySet的迭代次序是一样的,就是返回的数值不同,所以可以把
Entry的遍历方式推到上层,而values和keys分别留给子类实现;

  1.   private class KeyIterator extends LinkedHashIterator<K> {
  2.     public K next() { return nextEntry().getKey(); }
  3.     }
  4.     private class KeyIterator extends LinkedHashIterator<K> {
  5.     public K next() { return nextEntry().getKey(); }
  6.     }
  7.     private class ValueIterator extends LinkedHashIterator<V> {
  8.     public V next() { return nextEntry().value; }
  9.     }
  10.     private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {
  11.     public Map.Entry<K,V> next() { return nextEntry(); }
  12.     }

这里我个人觉得是处理得十分巧妙的地方;

抱歉!评论已关闭.