9.Java SDK源码分析系列笔记-LinkedHashMap

1. 是什么

  • 使用双向链表+HashMap(数组+链表+红黑树)实现
  • 相比于HashMap保存了顺序
    • 迭代时输出的顺序是
      • 按照插入节点的顺序来输出
      • 也可以指定成按照访问的顺序输出(LRU)

2. 使用

  • 按照插入节点的顺序来输出
public class LinkedHashMapTest {     public static void main(String[] args)     {         LinkedHashMap<String,Object> map = new LinkedHashMap<>();         map.put("name","zsk");         map.put("age",24);         map.put("height", 172L);          Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();         while (iterator.hasNext())         {             Map.Entry<String, Object> entry = iterator.next();             /*插入的顺序是怎样那么输出就是怎样             *   name=zsk                 age=24                 height=172             * */             System.out.println(entry);         }         //上面的输出跟这个一样          for (Map.Entry<String, Object> entry : map.entrySet())         {             /*插入的顺序是怎样那么输出就是怎样             *   name=zsk                 age=24                 height=172             * */             System.out.println(entry);         }          System.out.println(map.containsKey("name"));//true         System.out.println(map.get("name"));//zsk         map.remove("name");         System.out.println(map.containsKey("name"));//false     } } 
  • 按照访问的顺序输出
public class LinkedHashMapTest {     public static void main(String[] args)     {         LinkedHashMap<String, Object> map = new LinkedHashMap<>(2, 0.75F, true);         map.put("1", "a");         map.put("2", "b");         map.put("3", "c");           Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();         while (iterator.hasNext())         {             Map.Entry<String, Object> entry = iterator.next();             //            1=a             //            2=b             //            3=c             System.out.println(entry);         }         //上面的输出跟这个一样          for (Map.Entry<String, Object> entry : map.entrySet())         {             //            1=a             //            2=b             //            3=c             System.out.println(entry);         }          System.out.println(map.get("1"));//访问了1,那么1所在的节点被移到链表末尾         for (Map.Entry<String, Object> entry : map.entrySet())         {             //最近被访问的节点被放到链表末尾             //            2=b             //            3=c             //            1=a             System.out.println(entry);         }     } }   

3. 实现

3.1. uml

9.Java SDK源码分析系列笔记-LinkedHashMap
继承了HashMap,可克隆,可序列化

3.2. 构造方法

public class LinkedHashMap<K,V>     extends HashMap<K,V>//继承HashMap      implements Map<K,V> { 	//链表的节点。双向链表 	 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); 		    } 		}       //双向链表的头尾节点     transient LinkedHashMap.Entry<K,V> head;     transient LinkedHashMap.Entry<K,V> tail;      //true的话保持访问的顺序,false的话保持插入的顺序     final boolean accessOrder; 	public LinkedHashMap() { 		//调用HashMap的构造方法 	    super(); 	    accessOrder = false; 	} } 

LinkedHashMap继承了HashMap,所以map的get、set、remove等方法都是调用的HashMap的方法,而且LinkedHashMap还增强了HashMap的Node,定义了自己的Entry,加入了双向链表

3.3. put

其实就是调用的HashMap的put方法把插入新的节点或者替换value,只不过多了一些其他操作

  • HashMap.put
public V put(K key, V value) {     return putVal(hash(key), key, value, false, true); }  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,                boolean evict) {     Node<K,V>[] tab; Node<K,V> p; int n, i;     if ((tab = table) == null || (n = tab.length) == 0)         n = (tab = resize()).length;     if ((p = tab[i = (n - 1) & hash]) == null)         //LinkedHashMap重写了newNode方法         tab[i] = newNode(hash, key, value, null);     else {         Node<K,V> e; K k;         if (p.hash == hash &&             ((k = p.key) == key || (key != null && key.equals(k))))             e = p;         else if (p instanceof TreeNode)             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);         else {             for (int binCount = 0; ; ++binCount) {                 if ((e = p.next) == null) {                     p.next = newNode(hash, key, value, null);                     if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st                         treeifyBin(tab, hash);                     break;                 }                 if (e.hash == hash &&                     ((k = e.key) == key || (key != null && key.equals(k))))                     break;                 p = e;             }         }         //节点已经存在,只是更新value的情况         if (e != null) { // existing mapping for key             V oldValue = e.value;             if (!onlyIfAbsent || oldValue == null)                 e.value = value;             //需要维护双向链表中的顺序             afterNodeAccess(e);             return oldValue;         }     }     ++modCount;     if (++size > threshold)         resize();     //removeEldest方法返回true的时候,需要删除头节点【最少访问的节点】     afterNodeInsertion(evict);     return null; } 

需要注意的以下几点:

  • 12行:LinkedHashMap重写了newNode方法
  • 35-42行:更新value之后要维护双向链表中的顺序
  • 48行:不管是插入了新的节点还是更新了value,都需要根据情况(removeEldest方法是否返回true)删除链表头节点

3.3.1. 创建LinkedHashMap增强的节点--Entry【既是Node数组的节点又是双向链表的节点】

  • LinkedHashMap.newNode
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {     //创建的是LinkedHashMap增强的Entry     LinkedHashMap.Entry<K,V> p =         new LinkedHashMap.Entry<K,V>(hash, key, value, e);     //插入到链表尾部     linkNodeLast(p);     return p; } 
3.3.1.1. 创建的时候就把节点插入到双向链表尾部
  • linkNodeLast
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {     LinkedHashMap.Entry<K,V> last = tail;     tail = p;     //把当前节点插入到链表的末尾的操作     if (last == null)         head = p;     else {         p.before = last;         last.after = p;     } } 

3.3.2. put的节点(不是新插入的而是更新value),需要维护双向链表的顺序【输出的顺序】

//节点已经存在,只是更新value的情况     if (e != null) { // existing mapping for key         V oldValue = e.value;         if (!onlyIfAbsent || oldValue == null)             e.value = value;         //需要维护双向链表中的顺序         afterNodeAccess(e);         return oldValue;     } 
3.3.2.1. 把这个节点移动到双向链表尾部
  • afterNodeAccess
void afterNodeAccess(Node<K,V> e) { // move node to last     LinkedHashMap.Entry<K,V> last;     //指定了访问顺序 并且 当前节点不是尾巴节点【即不是新插入的节点】     if (accessOrder && (last = tail) != e) {         LinkedHashMap.Entry<K,V> p =             (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;         //以下的操作把当前节点移动到双向链表的末尾         p.after = null;         if (b == null)             head = a;         else             b.after = a;         if (a != null)             a.before = b;         else             last = b;         if (last == null)             head = p;         else {             p.before = last;             last.after = p;         }         tail = p;         ++modCount;     } } 

3.3.3. put的节点(不管是新插入的还是更新value)后判断是否需要删除头节点【最少访问的节点】

  • afterNodeInsertion
void afterNodeInsertion(boolean evict) { // possibly remove eldest     LinkedHashMap.Entry<K,V> first;     //removeEldestEntry返回true的时候【场景:比如定义LRU算法超过一定容量删除最少访问的节点】     if (evict && (first = head) != null && removeEldestEntry(first)) {        //删除头节点         K key = first.key;         removeNode(hash(key), key, null, false, true);     } } 

3.4. get

其实就是调用HashMap的getNode方法获取节点,然后在调用LinkedHashMap的afterNodeAccess把访问的当前节点放到链表的末尾

  • LinkedHashMap.get
public V get(Object key) {     Node<K,V> e; 	//调用HashMap的getNode获取节点     if ((e = getNode(hash(key), key)) == null)         return null;     //如果设置按照访问顺序的话,那么     if (accessOrder)         afterNodeAccess(e);          return e.value; } 
  • 7-8行:如果设置按照访问顺序的话,那么调用LinkedHashMap的afterNodeAccess把当前访问的节点移动到双向链表的末尾(最新的节点)

3.4.1. get节点后需要维护双向链表的顺序【输出的顺序】

//如果设置按照访问顺序的话,那么 if (accessOrder)     afterNodeAccess(e); 
3.4.1.1. 把访问的当前节点放到链表的末尾
  • LinkedHashMap afterNodeAccess
    同put操作一样,就是把访问的当前节点放到链表的末尾
void afterNodeAccess(Node<K,V> e) { // move node to last     LinkedHashMap.Entry<K,V> last;     if (accessOrder && (last = tail) != e) {     	//p代表当前节点,a代表后一个节点,b代表前一个节点         LinkedHashMap.Entry<K,V> p =             (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;         //前面节点的next指向后一个节点         p.after = null;         if (b == null)             head = a;         else             b.after = a;          //后一个节点的prev指向前一个节点         if (a != null)             a.before = b;         else             last = b;          //当前节点的prev指向尾节点,尾节点的next指向当前节点         if (last == null)             head = p;         else {             p.before = last;             last.after = p;         }         //更新尾节点为当前节点         tail = p;         ++modCount;     } } 

3.5. containsKey

就是调用HashMap的containsKey方法

public boolean containsKey(Object key) {     //依然是HashMap.getNode,没什么特殊的地方     return getNode(hash(key), key) != null; } 

3.6. containsValue

重写了HashMap的方法,效率更高

public boolean containsValue(Object value) { 	//遍历双向链表,效率O(N),不同于HashMap的O(N2)     for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {         V v = e.value;         if (v == value || (value != null && value.equals(v)))             return true;     }     return false; }  

3.7. remove

其实就是调用HashMap的remove方法删除节点,再调用LinkedHashMap的afterNodeRemoval把当前节点从双向链表中删除

  • HashMap.remove
public V remove(Object key) {     Node<K,V> e;     return (e = removeNode(hash(key), key, null, false, true)) == null ?         null : e.value; }  final Node<K,V> removeNode(int hash, Object key, Object value,                            boolean matchValue, boolean movable) {     Node<K,V>[] tab; Node<K,V> p; int n, index;     if ((tab = table) != null && (n = tab.length) > 0 &&         (p = tab[index = (n - 1) & hash]) != null) {         Node<K,V> node = null, e; K k; V v;         if (p.hash == hash &&             ((k = p.key) == key || (key != null && key.equals(k))))             node = p;         else if ((e = p.next) != null) {             if (p instanceof TreeNode)                 node = ((TreeNode<K,V>)p).getTreeNode(hash, key);             else {                 do {                     if (e.hash == hash &&                         ((k = e.key) == key ||                          (key != null && key.equals(k)))) {                         node = e;                         break;                     }                     p = e;                 } while ((e = e.next) != null);             }         }         if (node != null && (!matchValue || (v = node.value) == value ||                              (value != null && value.equals(v)))) {             if (node instanceof TreeNode)                 ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);             else if (node == p)                 tab[index] = node.next;             else                 p.next = node.next;             ++modCount;             --size;             //删除了节点后需要维护双向链表             afterNodeRemoval(node);             return node;         }     }     return null; } 

3.7.1. remove节点后需要从双向链表删除该节点

  • LinkedHashMap afterNodeRemoval
void afterNodeRemoval(Node<K,V> e) { // unlink     //以下的操作把当前删除的节点从双向链表中删除 	//p是当前被删除的节点,b是p的前一个节点,a是p的后一个节点     LinkedHashMap.Entry<K,V> p =         (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;     p.before = p.after = null;     //修改前一个节点的next指针     if (b == null)         head = a;     else         b.after = a;     //修改后一个节点的prev指针     if (a == null)         tail = b;     else         a.before = b; } 

3.8. entrySet

3.8.1. 要研究的代码

Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator(); 
  • LinkedHashMap entrySet
public Set<Map.Entry<K,V>> entrySet() {     Set<Map.Entry<K,V>> es;     //这个entrySet属性什么时候set的?     //返回LinkedEntrySet     return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es; } 

调用map.entrySet()返回的是LinkedEntrySet,如下

3.8.2. LinkedHashMap.entrySet()返回的是LinkedEntrySet

  • LinkedEntrySet
final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {     public final int size()                 { return size; }     public final void clear()               { LinkedHashMap.this.clear(); }     public final Iterator<Map.Entry<K,V>> iterator() {         //遍历器是LinkedEntryIterator         return new LinkedEntryIterator();     }     public final boolean contains(Object o) {         if (!(o instanceof Map.Entry))             return false;         Map.Entry<?,?> e = (Map.Entry<?,?>) o;         Object key = e.getKey();         Node<K,V> candidate = getNode(hash(key), key);         return candidate != null && candidate.equals(e);     }     public final boolean remove(Object o) {         if (o instanceof Map.Entry) {             Map.Entry<?,?> e = (Map.Entry<?,?>) o;             Object key = e.getKey();             Object value = e.getValue();             return removeNode(hash(key), key, value, true, true) != null;         }         return false;     }     public final Spliterator<Map.Entry<K,V>> spliterator() {         return Spliterators.spliterator(this, Spliterator.SIZED |                                         Spliterator.ORDERED |                                         Spliterator.DISTINCT);     }     public final void forEach(Consumer<? super Map.Entry<K,V>> action) {         if (action == null)             throw new NullPointerException();         int mc = modCount;         for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)             action.accept(e);         if (modCount != mc)             throw new ConcurrentModificationException();     } } 

再调用LinkedEntrySet的iterator返回的是LinkedEntryIterator

3.8.3. LinkedHashMap.entrySet().iterator()返回的是LinkedEntryIterator

final class LinkedEntryIterator extends LinkedHashIterator//继承了LinkedHashIterator         implements Iterator<Map.Entry<K,V>> {         //重写了next方法,调用LinkedHashIterator的nextNode         public final Map.Entry<K,V> next() { return nextNode(); }     } 

这个LinkedEntryIterator继承了LinkedHashIterator,如下

3.8.3.1. LinkedEntryIterator继承了LinkedHashIterator
  • LinkedHashIterator
 abstract class LinkedHashIterator {     LinkedHashMap.Entry<K,V> next;     LinkedHashMap.Entry<K,V> current;     int expectedModCount;      LinkedHashIterator() {     	//从链表头部开始遍历         next = head;         expectedModCount = modCount;         current = null;     }      public final boolean hasNext() {         return next != null;     }  	//next会调用这个方法     final LinkedHashMap.Entry<K,V> nextNode() {         LinkedHashMap.Entry<K,V> e = next;         if (modCount != expectedModCount)             throw new ConcurrentModificationException();         if (e == null)             throw new NoSuchElementException();         //链表中当前节点的下一个节点         current = e;         next = e.after;         return e;     }      public final void remove() {         Node<K,V> p = current;         if (p == null)             throw new IllegalStateException();         if (modCount != expectedModCount)             throw new ConcurrentModificationException();         current = null;         K key = p.key;     	//HashMap的removeNode方法         removeNode(hash(key), key, null, false, false);         expectedModCount = modCount;     } }  

可以看出迭代输出的时候是从头到尾输出的,也就是旧的先打印,然后在打印新的节点

4. 总结

在HashMap的基础上+双向链表实现的
迭代LinkedHashMap,就是从内部维护的双链表的表头开始循环输出。
而双链表节点的顺序在LinkedHashMap的get、put时都会更新。以满足按照插入顺序输出,还是访问顺序输出。

5. 参考

发表评论

评论已关闭。

相关文章