找回密码
 立即注册
首页 业界区 业界 9.Java SDK源码分析系列笔记-LinkedHashMap

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

挺喽蹂 2025-6-30 17:02:27
目录

  • 1. 是什么
  • 2. 使用
  • 3. 实现

    • 3.1. uml
    • 3.2. 构造方法
    • 3.3. put

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

        • 3.3.1.1. 创建的时候就把节点插入到双向链表尾部

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

        • 3.3.2.1. 把这个节点移动到双向链表尾部

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

    • 3.4. get

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

        • 3.4.1.1. 把访问的当前节点放到链表的末尾


    • 3.5. containsKey
    • 3.6. containsValue
    • 3.7. remove

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

    • 3.8. entrySet

      • 3.8.1. 要研究的代码
      • 3.8.2. LinkedHashMap.entrySet()返回的是LinkedEntrySet
      • 3.8.3. LinkedHashMap.entrySet().iterator()返回的是LinkedEntryIterator

        • 3.8.3.1. LinkedEntryIterator继承了LinkedHashIterator



  • 4. 总结
  • 5. 参考

1. 是什么


  • 使用双向链表+HashMap(数组+链表+红黑树)实现
  • 相比于HashMap保存了顺序

    • 迭代时输出的顺序是

      • 按照插入节点的顺序来输出
      • 也可以指定成按照访问的顺序输出(LRU)


2. 使用


  • 按照插入节点的顺序来输出
  1. public class LinkedHashMapTest
  2. {
  3.     public static void main(String[] args)
  4.     {
  5.         LinkedHashMap<String,Object> map = new LinkedHashMap<>();
  6.         map.put("name","zsk");
  7.         map.put("age",24);
  8.         map.put("height", 172L);
  9.         Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
  10.         while (iterator.hasNext())
  11.         {
  12.             Map.Entry<String, Object> entry = iterator.next();
  13.             /*插入的顺序是怎样那么输出就是怎样
  14.             *   name=zsk
  15.                 age=24
  16.                 height=172
  17.             * */
  18.             System.out.println(entry);
  19.         }
  20.         //上面的输出跟这个一样
  21.         for (Map.Entry<String, Object> entry : map.entrySet())
  22.         {
  23.             /*插入的顺序是怎样那么输出就是怎样
  24.             *   name=zsk
  25.                 age=24
  26.                 height=172
  27.             * */
  28.             System.out.println(entry);
  29.         }
  30.         System.out.println(map.containsKey("name"));//true
  31.         System.out.println(map.get("name"));//zsk
  32.         map.remove("name");
  33.         System.out.println(map.containsKey("name"));//false
  34.     }
  35. }
复制代码

  • 按照访问的顺序输出
  1. public class LinkedHashMapTest
  2. {
  3.     public static void main(String[] args)
  4.     {
  5.         LinkedHashMap<String, Object> map = new LinkedHashMap<>(2, 0.75F, true);
  6.         map.put("1", "a");
  7.         map.put("2", "b");
  8.         map.put("3", "c");
  9.         Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
  10.         while (iterator.hasNext())
  11.         {
  12.             Map.Entry<String, Object> entry = iterator.next();
  13.             //            1=a
  14.             //            2=b
  15.             //            3=c
  16.             System.out.println(entry);
  17.         }
  18.         //上面的输出跟这个一样
  19.         for (Map.Entry<String, Object> entry : map.entrySet())
  20.         {
  21.             //            1=a
  22.             //            2=b
  23.             //            3=c
  24.             System.out.println(entry);
  25.         }
  26.         System.out.println(map.get("1"));//访问了1,那么1所在的节点被移到链表末尾
  27.         for (Map.Entry<String, Object> entry : map.entrySet())
  28.         {
  29.             //最近被访问的节点被放到链表末尾
  30.             //            2=b
  31.             //            3=c
  32.             //            1=a
  33.             System.out.println(entry);
  34.         }
  35.     }
  36. }
复制代码
3. 实现

3.1. uml

1.png

继承了HashMap,可克隆,可序列化
3.2. 构造方法
  1. public class LinkedHashMap<K,V>
  2.     extends HashMap<K,V>//继承HashMap
  3.     implements Map<K,V>
  4. {
  5.         //链表的节点。双向链表
  6.          static class Entry<K,V> extends HashMap.Node<K,V> {
  7.                     Entry<K,V> before, after;
  8.                     Entry(int hash, K key, V value, Node<K,V> next) {
  9.                         super(hash, key, value, next);
  10.                     }
  11.                 }
  12.     //双向链表的头尾节点
  13.     transient LinkedHashMap.Entry<K,V> head;
  14.     transient LinkedHashMap.Entry<K,V> tail;
  15.     //true的话保持访问的顺序,false的话保持插入的顺序
  16.     final boolean accessOrder;
  17.         public LinkedHashMap() {
  18.                 //调用HashMap的构造方法
  19.             super();
  20.             accessOrder = false;
  21.         }
  22. }
复制代码
LinkedHashMap继承了HashMap,所以map的get、set、remove等方法都是调用的HashMap的方法,而且LinkedHashMap还增强了HashMap的Node,定义了自己的Entry,加入了双向链表
3.3. put

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

  • HashMap.put
  1. public V put(K key, V value) {
  2.     return putVal(hash(key), key, value, false, true);
  3. }
  4. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  5.                boolean evict) {
  6.     Node<K,V>[] tab; Node<K,V> p; int n, i;
  7.     if ((tab = table) == null || (n = tab.length) == 0)
  8.         n = (tab = resize()).length;
  9.     if ((p = tab[i = (n - 1) & hash]) == null)
  10.         //LinkedHashMap重写了newNode方法
  11.         tab[i] = newNode(hash, key, value, null);
  12.     else {
  13.         Node<K,V> e; K k;
  14.         if (p.hash == hash &&
  15.             ((k = p.key) == key || (key != null && key.equals(k))))
  16.             e = p;
  17.         else if (p instanceof TreeNode)
  18.             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  19.         else {
  20.             for (int binCount = 0; ; ++binCount) {
  21.                 if ((e = p.next) == null) {
  22.                     p.next = newNode(hash, key, value, null);
  23.                     if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  24.                         treeifyBin(tab, hash);
  25.                     break;
  26.                 }
  27.                 if (e.hash == hash &&
  28.                     ((k = e.key) == key || (key != null && key.equals(k))))
  29.                     break;
  30.                 p = e;
  31.             }
  32.         }
  33.         //节点已经存在,只是更新value的情况
  34.         if (e != null) { // existing mapping for key
  35.             V oldValue = e.value;
  36.             if (!onlyIfAbsent || oldValue == null)
  37.                 e.value = value;
  38.             //需要维护双向链表中的顺序
  39.             afterNodeAccess(e);
  40.             return oldValue;
  41.         }
  42.     }
  43.     ++modCount;
  44.     if (++size > threshold)
  45.         resize();
  46.     //removeEldest方法返回true的时候,需要删除头节点【最少访问的节点】
  47.     afterNodeInsertion(evict);
  48.     return null;
  49. }
复制代码
需要注意的以下几点:

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


  • LinkedHashMap.newNode
  1. Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
  2.     //创建的是LinkedHashMap增强的Entry
  3.     LinkedHashMap.Entry<K,V> p =
  4.         new LinkedHashMap.Entry<K,V>(hash, key, value, e);
  5.     //插入到链表尾部
  6.     linkNodeLast(p);
  7.     return p;
  8. }
复制代码
3.3.1.1. 创建的时候就把节点插入到双向链表尾部


  • linkNodeLast
  1. private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
  2.     LinkedHashMap.Entry<K,V> last = tail;
  3.     tail = p;
  4.     //把当前节点插入到链表的末尾的操作
  5.     if (last == null)
  6.         head = p;
  7.     else {
  8.         p.before = last;
  9.         last.after = p;
  10.     }
  11. }
复制代码
3.3.2. put的节点(不是新插入的而是更新value),需要维护双向链表的顺序【输出的顺序】
  1. //节点已经存在,只是更新value的情况
  2.     if (e != null) { // existing mapping for key
  3.         V oldValue = e.value;
  4.         if (!onlyIfAbsent || oldValue == null)
  5.             e.value = value;
  6.         //需要维护双向链表中的顺序
  7.         afterNodeAccess(e);
  8.         return oldValue;
  9.     }
复制代码
3.3.2.1. 把这个节点移动到双向链表尾部


  • afterNodeAccess
  1. void afterNodeAccess(Node<K,V> e) { // move node to last
  2.     LinkedHashMap.Entry<K,V> last;
  3.     //指定了访问顺序 并且 当前节点不是尾巴节点【即不是新插入的节点】
  4.     if (accessOrder && (last = tail) != e) {
  5.         LinkedHashMap.Entry<K,V> p =
  6.             (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
  7.         //以下的操作把当前节点移动到双向链表的末尾
  8.         p.after = null;
  9.         if (b == null)
  10.             head = a;
  11.         else
  12.             b.after = a;
  13.         if (a != null)
  14.             a.before = b;
  15.         else
  16.             last = b;
  17.         if (last == null)
  18.             head = p;
  19.         else {
  20.             p.before = last;
  21.             last.after = p;
  22.         }
  23.         tail = p;
  24.         ++modCount;
  25.     }
  26. }
复制代码
3.3.3. put的节点(不管是新插入的还是更新value)后判断是否需要删除头节点【最少访问的节点】


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

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

  • LinkedHashMap.get
  1. public V get(Object key) {
  2.     Node<K,V> e;
  3.         //调用HashMap的getNode获取节点
  4.     if ((e = getNode(hash(key), key)) == null)
  5.         return null;
  6.     //如果设置按照访问顺序的话,那么
  7.     if (accessOrder)
  8.         afterNodeAccess(e);
  9.    
  10.     return e.value;
  11. }
复制代码

  • 7-8行:如果设置按照访问顺序的话,那么调用LinkedHashMap的afterNodeAccess把当前访问的节点移动到双向链表的末尾(最新的节点)
3.4.1. get节点后需要维护双向链表的顺序【输出的顺序】
  1. //如果设置按照访问顺序的话,那么
  2. if (accessOrder)
  3.     afterNodeAccess(e);
复制代码
3.4.1.1. 把访问的当前节点放到链表的末尾


  • LinkedHashMap afterNodeAccess
    同put操作一样,就是把访问的当前节点放到链表的末尾
  1. void afterNodeAccess(Node<K,V> e) { // move node to last
  2.     LinkedHashMap.Entry<K,V> last;
  3.     if (accessOrder && (last = tail) != e) {
  4.             //p代表当前节点,a代表后一个节点,b代表前一个节点
  5.         LinkedHashMap.Entry<K,V> p =
  6.             (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
  7.         //前面节点的next指向后一个节点
  8.         p.after = null;
  9.         if (b == null)
  10.             head = a;
  11.         else
  12.             b.after = a;
  13.         //后一个节点的prev指向前一个节点
  14.         if (a != null)
  15.             a.before = b;
  16.         else
  17.             last = b;
  18.         //当前节点的prev指向尾节点,尾节点的next指向当前节点
  19.         if (last == null)
  20.             head = p;
  21.         else {
  22.             p.before = last;
  23.             last.after = p;
  24.         }
  25.         //更新尾节点为当前节点
  26.         tail = p;
  27.         ++modCount;
  28.     }
  29. }
复制代码
3.5. containsKey

就是调用HashMap的containsKey方法
  1. public boolean containsKey(Object key) {
  2.     //依然是HashMap.getNode,没什么特殊的地方
  3.     return getNode(hash(key), key) != null;
  4. }
复制代码
3.6. containsValue

重写了HashMap的方法,效率更高
  1. public boolean containsValue(Object value) {
  2.         //遍历双向链表,效率O(N),不同于HashMap的O(N2)
  3.     for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
  4.         V v = e.value;
  5.         if (v == value || (value != null && value.equals(v)))
  6.             return true;
  7.     }
  8.     return false;
  9. }
复制代码
3.7. remove

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

  • HashMap.remove
  1. public V remove(Object key) {
  2.     Node<K,V> e;
  3.     return (e = removeNode(hash(key), key, null, false, true)) == null ?
  4.         null : e.value;
  5. }
  6. final Node<K,V> removeNode(int hash, Object key, Object value,
  7.                            boolean matchValue, boolean movable) {
  8.     Node<K,V>[] tab; Node<K,V> p; int n, index;
  9.     if ((tab = table) != null && (n = tab.length) > 0 &&
  10.         (p = tab[index = (n - 1) & hash]) != null) {
  11.         Node<K,V> node = null, e; K k; V v;
  12.         if (p.hash == hash &&
  13.             ((k = p.key) == key || (key != null && key.equals(k))))
  14.             node = p;
  15.         else if ((e = p.next) != null) {
  16.             if (p instanceof TreeNode)
  17.                 node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
  18.             else {
  19.                 do {
  20.                     if (e.hash == hash &&
  21.                         ((k = e.key) == key ||
  22.                          (key != null && key.equals(k)))) {
  23.                         node = e;
  24.                         break;
  25.                     }
  26.                     p = e;
  27.                 } while ((e = e.next) != null);
  28.             }
  29.         }
  30.         if (node != null && (!matchValue || (v = node.value) == value ||
  31.                              (value != null && value.equals(v)))) {
  32.             if (node instanceof TreeNode)
  33.                 ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
  34.             else if (node == p)
  35.                 tab[index] = node.next;
  36.             else
  37.                 p.next = node.next;
  38.             ++modCount;
  39.             --size;
  40.             //删除了节点后需要维护双向链表
  41.             afterNodeRemoval(node);
  42.             return node;
  43.         }
  44.     }
  45.     return null;
  46. }
复制代码
3.7.1. remove节点后需要从双向链表删除该节点


  • LinkedHashMap afterNodeRemoval
  1. void afterNodeRemoval(Node<K,V> e) { // unlink
  2.     //以下的操作把当前删除的节点从双向链表中删除
  3.         //p是当前被删除的节点,b是p的前一个节点,a是p的后一个节点
  4.     LinkedHashMap.Entry<K,V> p =
  5.         (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
  6.     p.before = p.after = null;
  7.     //修改前一个节点的next指针
  8.     if (b == null)
  9.         head = a;
  10.     else
  11.         b.after = a;
  12.     //修改后一个节点的prev指针
  13.     if (a == null)
  14.         tail = b;
  15.     else
  16.         a.before = b;
  17. }
复制代码
3.8. entrySet

3.8.1. 要研究的代码
  1. Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
复制代码

  • LinkedHashMap entrySet
  1. public Set<Map.Entry<K,V>> entrySet() {
  2.     Set<Map.Entry<K,V>> es;
  3.     //这个entrySet属性什么时候set的?
  4.     //返回LinkedEntrySet
  5.     return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
  6. }
复制代码
调用map.entrySet()返回的是LinkedEntrySet,如下
3.8.2. LinkedHashMap.entrySet()返回的是LinkedEntrySet


  • LinkedEntrySet
[code]final class LinkedEntrySet extends AbstractSet {    public final int size()                 { return size; }    public final void clear()               { LinkedHashMap.this.clear(); }    public final Iterator 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 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 spliterator() {        return Spliterators.spliterator(this, Spliterator.SIZED |                                        Spliterator.ORDERED |                                        Spliterator.DISTINCT);    }    public final void forEach(Consumer
您需要登录后才可以回帖 登录 | 立即注册