飞道的博客

数据结构 - HashMap 看这一篇就够了

345人阅读  评论(0)

简介

HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对


  
  1. public class HashMap<K,V> extends AbstractMap<K,V>
  2.          implements Map< K, V>, Cloneable, Serializable

属性


  
  1. // 默认数组长度 16
  2. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  3. // 最大数组长度
  4. static final int MAXIMUM_CAPACITY = 1 << 30;
  5. // 默认加载因子
  6. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  7. // 链表转红黑树阈值
  8. static final int TREEIFY_THRESHOLD = 8;
  9. // 红黑树转链表阈值
  10. static final int UNTREEIFY_THRESHOLD = 6;
  11. // 链表转红黑树限制
  12. static final int MIN_TREEIFY_CAPACITY = 64;
  13. // 元素数组
  14. transient Node<K,V>[] table;
  15. // 将数据转换成set的另一种存储形式,这个变量主要用于迭代功能
  16. transient Set<Entry<K,V>> entrySet;
  17. // 数组长度
  18. transient int size;
  19. // 修改次数
  20. transient int modCount;
  21. // 扩容阈值
  22. int threshold;
  23. // 实际加载因子
  24. final float loadFactor;

基础方法


  
  1. public int size() {
  2.     return size;
  3. }
  4. public boolean isEmpty() {
  5.     return size == 0;
  6. }
  7. public boolean containsKey(Object key) {
  8.     return getNode(hash(key), key) != null;
  9. }

获取Hash


  
  1. static final int hash(Object key) {
  2.      int h;
  3.      return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  4. }

从源码中可以看出,hash算法实际上就键的hashCode与hashCode无符号右移16位异或,为什么要搞这么麻烦呢?hashCode范围-2147483648到2147483648,这么大的数HashMap根本放不下,那么他是如何确定元素所在的数组下标呢?没错就是通过余数,正常情况下我们取余通过%,例如 


  
  1. 9%2= 1
  2. 9%4= 1

在特殊情况下针对这种除数是2的n次幂还可以用&,例如 


  
  1. 1001& 0001= 19%2或者 9&( 2- 1))
  2. 1001& 0011= 19%4或者 9&( 4- 1))

二进制中,一个数右移1位相当于除以2的商,而恰巧被移除出去的那一位就是除以2得到的余数,例如

9>>1 二进制 1001>>1=100,结果为49除以2等于41001向右移1位被移除的是192等于1

不仅是除以2,对于一个数要除以2的n次幂,也就是相当于把这个数向右移n位,而被移出去的n位即正好是我们要求是余数。例如


  
  1. 9 >> 2 二进制 1001>> 2= 10,结果为 2
  2.     9除以 422次幂)等于 21001向右移 2位被移除的是 194等于 1
  3. 19 >> 3 二进制 10011>> 3= 10,结果为 2
  4.     19除以 823次幂)等于 210011向右移 3位被移除的是 3198等于 3

对于除数是2的n次方的算式,我们只需要得到被除数的低n位就可以了,而正好,对于2的n次方这样的数,我们将其转换为二进制之后,它就是第n+1位为1,其余低位都为0的数,因此我们将其减1,就得到了第n+1位为0,而其他位都为1的数,用此数与被除数k进行位与运算,就得到了被除数的低n位二进制数

若一个数m满足:m=2n,那么k % m = k & (m-1)

通过上面可以的值hashCode&(16-1)可以得到余数,把前面换成hashCode&1111可以看出,实际上只有hashCode后4位参与运算,前面都是无效数据(都被0替换)。这样算出来的散列效果并不好,有没有办法把前面也参与运算?于是就有了高位与地位先异或一次,让结果中包含高位特征,然后在取余。

计算长度


  
  1. static final int tableSizeFor(int cap) {
  2.      int n = cap - 1;
  3.     n |= n >>> 1;
  4.     n |= n >>> 2;
  5.     n |= n >>> 4;
  6.     n |= n >>> 8;
  7.     n |= n >>> 16;
  8.      return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  9. }

此方法返回一个比给定整数大且最接近的2的幂次方整数,先排除cap - 1以9为例


  
  1. 9 >>> 1 二进制 1001>>> 1= 10049 |4二进制1001 | 100= 110113
  2. 13 >>> 2 二进制 1101>>> 2= 11313 |3二进制1101 | 11= 111115
  3. 15 >>> 4 二进制 1111>>> 4= 0315 |0二进制1111 | 0= 111115
  4. 15 >>> 8 二进制 1111>>> 8= 0315 |0二进制1111 | 0= 111115
  5. 15 >>> 16 二进制 1111>>> 16= 0315 |0二进制1111 | 0= 111115

最后n+1即16,这里你发现了什么?9的二进制1001最后全变成了1111,实际上就是把这个数所有为都变成1,最后在加1一个是2的n次幂(2的n次幂,转换二进制就是n+1位为1其他位都是0,2的n次幂减一,转换二进制就是从n位开始都是1)。如果cap为2的n次幂时,n+1位都会变成1,这样都超过我们想要的值了,所以要cap-1。(负数补码最终都会变成-1)

构造函数


  
  1. public HashMap(int initialCapacity, float loadFactor) {
  2.      // 判断长度是否越界
  3.      if (initialCapacity < 0)
  4.          throw new IllegalArgumentException( "Illegal initial capacity: " +
  5.                 initialCapacity);
  6.      if (initialCapacity > MAXIMUM_CAPACITY)
  7.         initialCapacity = MAXIMUM_CAPACITY;
  8.      // 判断加载因子不能小于等于0,也不能是无穷值NAN
  9.      if (loadFactor <= 0 || Float.isNaN(loadFactor))
  10.          throw new IllegalArgumentException( "Illegal load factor: " +
  11.                 loadFactor);
  12.      // 设置加载因子
  13.      this.loadFactor = loadFactor;
  14.     // 计算扩容阈值(初始化时阈值并没有乘以加载因子)
  15.      this.threshold = tableSizeFor(initialCapacity);
  16. }

此构造函数只设置了加载因子和计算下次扩容阈值,并没有创建数组


  
  1. public HashMap(int initialCapacity) {
  2.      // 调用两个参数的构造函数,加载因子使用默认值
  3.      this(initialCapacity, DEFAULT_LOAD_FACTOR);
  4. }

此构造函数调用上面构造函数初始化


  
  1. public HashMap() {
  2.      // 设置默认加载因子
  3.      this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  4. }

最常用的构造函数,除了设置默认加载因子,其实什么事也没干


  
  1. public HashMap(Map<? extends K, ? extends V> m) {
  2.      // 设置默认加载因子
  3.      this.loadFactor = DEFAULT_LOAD_FACTOR;
  4.     putMapEntries(m, false);
  5. }

调用putMapEntries方法,方法如下


  
  1. final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
  2.      // 获取m长度
  3.      int s = m.size();
  4.      // 判断长度大于0
  5.      if (s > 0) {
  6.          // 当前table为空
  7.          if (table == null) {
  8.              // 长度除以加载因子(5/0.75=6.666...),算长度时只能算整数所以加1
  9.              float ft = (( float)s / loadFactor) + 1.0F;
  10.              int t = ((ft < ( float)MAXIMUM_CAPACITY) ?
  11.                     ( int)ft : MAXIMUM_CAPACITY);
  12.              // 以m算出来的数组长度大于扩容阈值,则修改扩容阈值
  13.              // 当前table为空,所以会用这个阈值当数组初始化长度
  14.              if (t > threshold)
  15.                 threshold = tableSizeFor(t);
  16.         }
  17.          else if (s > threshold)
  18.              // 长度大于扩容阈值,先扩容一次
  19.             resize();
  20.          // 遍历参数m的所有entry
  21.          for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
  22.             K key = e.getKey();
  23.             V value = e.getValue();
  24.              // 向当前map中添加元素
  25.             putVal(hash(key), key, value, false, evict);
  26.         }
  27.     }
  28. }

键取值


  
  1. public V get(Object key) {
  2.     // 声明节点
  3.     Node<K,V> e;
  4.     // 调用getNode获取节点,不为空时取值
  5.     return (e = getNode(hash(key), key)) == null ? null : e.value;
  6. }
  7. final Node<K,V> getNode(int hash, Object key) {
  8.     // 声明变量
  9.     Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
  10.     // 判断数组是否为空,判断元素个数是否等于0,判断数组中头元素是否为空
  11.     if ((tab = table) != null && (n = tab.length) > 0 &&
  12.             (first = tab[(n - 1) & hash]) != null) {
  13.         // 判断头元素hash是否一样,一样时判断键是否一样
  14.         if (first.hash == hash &&
  15.                 ((k = first.key) == key || (key != null && key.equals(k))))
  16.             // 都一样返回头元素
  17.             return first;
  18.         // 不一样找下级元素
  19.         if ((e = first.next) != null) {
  20.             // 头元素为树节点去红黑树中找
  21.             if (first instanceof TreeNode)
  22.                 return ((TreeNode<K,V>)first).getTreeNode(hash, key);
  23.             // 头节点不为树节点,就是链表遍历链表查找
  24.             do {
  25.                 // hash一样,键一样就返回当前元素
  26.                 if (e.hash == hash &&
  27.                         ((k = e.key) == key || (key != null && key.equals(k))))
  28.                     return e;
  29.             // 给e赋,为空时退出循环    
  30.             } while ((e = e.next) != null);
  31.         }
  32.     }
  33.     // 没找到返回null
  34.     return null;
  35. }

添加方法
 


  
  1. public V put(K key, V value) {
  2.     // 调用putVal方法
  3.     return putVal(hash(key), key, value, false, true);
  4. }
  5. // onlyIfAbsent 表示是否仅在 oldValue 为 null 的情况下更新键值对的值
  6. // evict 如果为false,则表处于创建阶段
  7. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  8.                 boolean evict) {
  9.     // 声明变量
  10.     Node<K,V>[] tab; Node<K,V> p; int n, i;
  11.     // 判断数组是否为空,判断数组长度是否为0
  12.     if ((tab = table) == null || (n = tab.length) == 0)
  13.         // 开始初始化(使用下次扩容阈值扩容并返回长度)
  14.         n = (tab = resize()).length;
  15.  
  16.     // 通过取余得到索引,通过索引找到槽,判断槽中是否有元素
  17.     if ((p = tab[i = (n - 1) & hash]) == null)
  18.         // 没有元素时,构建元素并存入
  19.         tab[i] = newNode(hash, key, value, null);
  20.     else { // 槽中有元素的逻辑(p不为空)
  21.         // 声明临时变量
  22.         Node<K,V> e; K k;
  23.         // 如果键跟头元素一样,则将e指向该键值对
  24.         if (p.hash == hash &&
  25.                 ((k = p.key) == key || (key != null && key.equals(k))))
  26.             // 一样赋值给e
  27.             e = p;
  28.         // 如果p是树节点,即走红黑树插入逻辑
  29.         else if (p instanceof TreeNode)
  30.             e = ((TreeNode<K,V>)p).putTreeVal( this, tab, hash, key, value);
  31.         else {
  32.             // 最后走链表逻辑
  33.             for ( int binCount = 0; ; ++binCount) {
  34.                 // 直到找到尾部
  35.                 if ((e = p.next) == null) {
  36.                     // 能进入到这儿,e一定为空,表示没有重复建
  37.                     p.next = newNode(hash, key, value, null);
  38.                     // binCount从0开始,大于等于7就会尝试链变树
  39.                     // 判断前已经添加,所以binCount=7时实际元素是8
  40.                     // 前面又加了一个,所以从第9个开始尝试变树
  41.                     if (binCount >= TREEIFY_THRESHOLD - 1)
  42.                         // 尝试变树
  43.                         treeifyBin(tab, hash);
  44.                     break;
  45.                 }
  46.                 // 如果键跟e一样,则跳出循环
  47.                 if (e.hash == hash &&
  48.                         ((k = e.key) == key || (key != null && key.equals(k))))
  49.                     break;
  50.                 // 当前轮e赋值给p
  51.                 p = e;
  52.             }
  53.         }
  54.         // 当e不为空,也就是有重复值
  55.         if (e != null) {
  56.             V oldValue = e.value;
  57.             // 原值为空时,无论onlyIfAbsent是什么,都会被新值覆盖
  58.             if (!onlyIfAbsent || oldValue == null)
  59.                 e.value = value;
  60.             // 此方法在HashMap中是空方法,LinkedHashMap中会讲
  61.             afterNodeAccess(e);
  62.             // 返回原值
  63.             return oldValue;
  64.         }
  65.     }
  66.     // 能走到这儿,至少说明没有重复建(有重复前面已经返回了)
  67.     // 并且添加新元素成功,修改次数加1
  68.     ++modCount;
  69.     // size加1,并判断是否达到下次扩容阈值
  70.     if (++size > threshold)
  71.         // 扩容
  72.         resize();
  73.     // 此方法在HashMap中是空方法,LinkedHashMap中会讲
  74.     afterNodeInsertion(evict);
  75.     // 添加成功返回空
  76.     return null;
  77. }

从putVal源代码中我们可以知道,当插入一个元素成功的时候size就加1,若size大于threshold的时候,就会进行扩容(1、替换元素不会触发扩容,2、先添加元素在判断扩容)。按数组默认长16,扩容法值12,当我们加完第13个元素后开始扩容,若果中间有hash冲突可能只用了10个槽,一样会扩容,HashMap是否会空槽,跟hash算法有关。扩容会遍历所有的元素,时间复杂度很高,但是扩容处理后,元素会更加均匀的分布在各个槽中,会提升访问效率。

链表转树前置方法


  
  1. final void treeifyBin(Node<K,V>[] tab, int hash) {
  2.     int n, index; Node<K,V> e;
  3.     // 只有在添加时,链表长度超过8才会调用这个方法
  4.     // 数组为空或者,数组长度小于64,不管有没有达到扩容阈值都会扩容一次
  5.     if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
  6.         resize();
  7.     // 槽中链表头元素不为空
  8.     else if ((e = tab[index = (n - 1) & hash]) != null) {
  9.         TreeNode<K,V> hd = null, tl = null;
  10.         do {
  11.             // 普通节点转为树节点
  12.             TreeNode<K,V> p = replacementTreeNode(e, null);
  13.             // 当前轮临时元素为空(第一次)
  14.             if (tl == null)
  15.                 // 设置头元素
  16.                 hd = p;
  17.             else {
  18.                 // 设置新节点前面节点为当前轮tl
  19.                 p.prev = tl;
  20.                 // 当前轮临时结点下一个结点设置为新节点
  21.                 tl.next = p;
  22.             }
  23.             // 节点下移
  24.             tl = p;
  25.         } while ((e = e.next) != null); // 原链表中节点末尾退出
  26.         // 替换槽中整条链,当新双向链表不为空时(节点已经替换成树节点)
  27.         if ((tab[index] = hd) != null)
  28.             // 双向链表转红黑树
  29.             hd.treeify(tab);
  30.     }
  31. }

扩容
 


  
  1. final Node<K,V>[] resize() {
  2.     // 原数组
  3.     Node<K,V>[] oldTab = table;
  4.     // 原数组长度
  5.     int oldCap = (oldTab == null) ? 0 : oldTab.length;
  6.     // 原扩容阈值
  7.     int oldThr = threshold;
  8.     // 新长度,新扩容阈值
  9.     int newCap, newThr = 0;
  10.     // 原数组长度大于0
  11.     if (oldCap > 0) {
  12.         // 是否达到上限
  13.         if (oldCap >= MAXIMUM_CAPACITY) {
  14.             // 达到上限不在扩容
  15.             threshold = Integer.MAX_VALUE;
  16.             return oldTab;
  17.         }
  18.         // 原长度扩容一倍,并且新长度小于上限,原长度大于16
  19.         else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
  20.                 oldCap >= DEFAULT_INITIAL_CAPACITY)
  21.             // 新扩容阈值也增加一倍
  22.             newThr = oldThr << 1; // double threshold
  23.     }
  24.     // 原扩容阈值大于0
  25.     else if (oldThr > 0)
  26.         // 原数组长度为0,原扩容阈值大于0,只有在初始化时存在
  27.         newCap = oldThr;
  28.     else {
  29.         // HashMap新建状态赋值
  30.         newCap = DEFAULT_INITIAL_CAPACITY;
  31.         newThr = ( int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
  32.     }
  33.  
  34.     // 新扩容阈值为0(只有在初始化时,走到这儿才会新扩容阈值等于0)
  35.     if (newThr == 0) {
  36.         // 计算新扩容阈值(新长度*加载因子,并且小于上限)
  37.         float ft = ( float)newCap * loadFactor;
  38.         newThr = (newCap < MAXIMUM_CAPACITY && ft < ( float)MAXIMUM_CAPACITY ?
  39.                 ( int)ft : Integer.MAX_VALUE);
  40.     }
  41.     // 赋值新扩容阈值
  42.     threshold = newThr;
  43.     // 创建新数组
  44.     @SuppressWarnings({ "rawtypes", "unchecked"})
  45.     Node<K,V>[] newTab = (Node<K,V>[]) new Node[newCap];
  46.     table = newTab;
  47.     // 以下逻辑是元素迁移逻辑
  48.     if (oldTab != null) {
  49.         // 遍历原数组
  50.         for ( int j = 0; j < oldCap; ++j) {
  51.             Node<K,V> e;
  52.             // e这时为槽里第一个元素(无论是链表还是红黑树,找到头元素,下面的都可以获得)
  53.             if ((e = oldTab[j]) != null) {
  54.                 // 头元素拿出来后,当前槽清空
  55.                 oldTab[j] = null;
  56.                 // 只有一个元素
  57.                 if (e.next == null)
  58.                     // 只有一个元素时,直接放入新数组槽中
  59.                     newTab[e.hash & (newCap - 1)] = e;
  60.                 // 红黑树
  61.                 else if (e instanceof TreeNode)
  62.                     // 请看TreeNode的split方法
  63.                     ((TreeNode<K,V>)e).split( this, newTab, j, oldCap);
  64.                 // 链表
  65.                 else {
  66.                     // loHead 原槽中头节点,loTail 原链当前节点
  67.                     Node<K,V> loHead = null, loTail = null;
  68.                     // hiHead 扩容槽中头节点,hiTail 扩容链当前节点
  69.                     Node<K,V> hiHead = null, hiTail = null;
  70.                     // 下级元素
  71.                     Node<K,V> next;
  72.                     // 遍历链表上所有元素
  73.                     do {
  74.                         // 先取出当前元素的下级元素
  75.                         next = e.next;
  76.                         // 判断是否可以放在原槽中(0可以,oldCap需要移动)
  77.                         if ((e.hash & oldCap) == 0) {
  78.                             // 头结点为空
  79.                             if (loTail == null)
  80.                                 // 当前结点赋给头结点
  81.                                 loHead = e;
  82.                             else
  83.                                 // 否则往后追加
  84.                                 loTail.next = e;
  85.                             // 相当于当前结点下移
  86.                             loTail = e;
  87.                         }
  88.                         else {
  89.                             // 扩容槽头节点为空
  90.                             if (hiTail == null)
  91.                                 // 赋值头节点
  92.                                 hiHead = e;
  93.                             else
  94.                                 // 头结点不为空往后追加
  95.                                 hiTail.next = e;
  96.                             // 扩容槽中当前节点下移
  97.                             hiTail = e;
  98.                         }
  99.                     } while ((e = next) != null);
  100.                     // 原链不为空
  101.                     if (loTail != null) {
  102.                         // 这一步主要作用是清空原结构冗余链
  103.                         loTail.next = null;
  104.                         // 整个头结点放入槽中
  105.                         newTab[j] = loHead;
  106.                     }
  107.                     if (hiTail != null) {
  108.                         // 这一步主要作用是清空原结构冗余链
  109.                         hiTail.next = null;
  110.                         // 整个扩容头结点放入扩容后槽中
  111.                         newTab[j + oldCap] = hiHead;
  112.                     }
  113.                 }
  114.             }
  115.         }
  116.     }
  117.     // 返回新数组
  118.     return newTab;
  119. }

很多人对(e.hash & oldCap) == 0可能会有疑问,这里解释一下。它的结果只能是0或者oldCap,认真看来hash获取的都知道取余hash&oldCap-1,那么扩容后取余方式hash&newCap-1,举个实际的例子
12%4,二进制1100 &   11 = 0    10进制0
12%8,二进制1100 & 1 11 = 100  10进制4
实际上,扩容后取余方式就是在前面又补1,后面的11都没用上(对于2n次幂肯定一样);那么扩容后这个元素是否需要移动到扩容后槽中,其实就看(newCap-1)最高位就行。既然我们只需要看这一位那我就把hash其他位全变成零,1 11中后面11换成0根hash位与操作就行,刚好(newCap-1)除了高位一外其他位换0就是oldCap。所以这er要么是0要么就是oldCap,需要往新槽里面移动的,只需要在原槽基础上加oldCap就可以了。

删除


  
  1. public V remove(Object key) {
  2.     Node<K,V> e;
  3.     return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value;
  4. }
  5. public boolean remove(Object key, Object value) {
  6.     return removeNode(hash(key), key, value, true, true) != null;
  7. }
  8. // matchValue 为true,则表示删除它key对应的value,不删除key,
  9. // movable 为false,则表示删除后,不移动节点
  10. final Node<K,V> removeNode(int hash, Object key, Object value,
  11.                             boolean matchValue, boolean movable) {
  12.     Node<K,V>[] tab; Node<K,V> p; int n, index;
  13.     // 哈希数组不为null,且长度大于0,然后获得到要删除key的节点所在是数组下标位置
  14.     if ((tab = table) != null && (n = tab.length) > 0 &&
  15.             (p = tab[index = (n - 1) & hash]) != null) {
  16.         // nodee 存储要删除的节点,e 临时变量,k 当前节点的key,v 当前节点的value
  17.         Node<K,V> node = null, e; K k; V v;
  18.         // 如果数组下标的节点正好是要删除的节点,把值赋给临时变量node
  19.         if (p.hash == hash &&
  20.                 ((k = p.key) == key || (key != null && key.equals(k))))
  21.             node = p;
  22.             // 链表或者红黑树
  23.         else if ((e = p.next) != null) {
  24.             if (p instanceof TreeNode)
  25.                 // 遍历红黑树,找到该节点并返回
  26.                 node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
  27.             else { // 表示为链表节点,一样的遍历找到该节点
  28.                 do {
  29.                     if (e.hash == hash &&
  30.                             ((k = e.key) == key ||
  31.                                     (key != null && key.equals(k)))) {
  32.                         node = e;
  33.                         break;
  34.                     }
  35.                     // 如果进入了链表中的遍历,那么此处的p不再是数组下标的节点,而是要删除结点的上一个结点
  36.                     p = e;
  37.                 } while ((e = e.next) != null);
  38.             }
  39.         }
  40.         // 找到要删除的节点后,判断!matchValue,我们正常的remove删除,!matchValue都为true
  41.         if (node != null && (!matchValue || (v = node.value) == value ||
  42.                 (value != null && value.equals(v)))) {
  43.             // 如果删除的节点是红黑树结构,则去红黑树中删除
  44.             if (node instanceof TreeNode)
  45.                 ((TreeNode<K,V>)node).removeTreeNode( this, tab, movable);
  46.                 // 如果是链表结构,且删除的节点为数组下标节点,也就是头结点,直接让下一个作为头
  47.             else if (node == p)
  48.                 tab[index] = node.next;
  49.             else // 为链表结构,删除的节点在链表中,把要删除的下一个结点设为上一个结点的下一个节点
  50.                 p.next = node.next;
  51.             // 修改计数器
  52.             ++modCount;
  53.             // 长度减1
  54.             --size;
  55.             // 此方法在hashMap中是为了让子类去实现,主要是对删除结点后的链表关系进行处理
  56.             afterNodeRemoval(node);
  57.             // 返回删除的节点
  58.             return node;
  59.         }
  60.     }
  61.     // 返回null则表示没有该节点,删除失败
  62.     return null;
  63. }

清除重置


  
  1. public void clear() {
  2.     Node<K,V>[] tab;
  3.     modCount++;
  4.     // 数组不为空
  5.     if ((tab = table) != null && size > 0) {
  6.         size = 0;
  7.         // 遍历数组
  8.         for ( int i = 0; i < tab.length; ++i)
  9.             // 所有槽清空
  10.             tab[i] = null;
  11.     }
  12. }

可以看出,HashMap并没有清除所有元素,只是清空所有槽,并且不会改变槽个数


  
  1. void reinitialize() {
  2.     table = null;
  3.     entrySet = null;
  4.     keySet = null;
  5.     values = null;
  6.     modCount = 0;
  7.     threshold = 0;
  8.     size = 0;
  9. }

清空所有数据

创建、转型节点


  
  1. Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
  2.     return new Node<>(hash, key, value, next);
  3. }

创建新普通节点


  
  1. Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
  2.     return new Node<>(p.hash, p.key, p.value, next);
  3. }

其他节点转换为普通节点


  
  1. TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
  2.     return new TreeNode<>(hash, key, value, next);
  3. }

创建树节点


  
  1. TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
  2.     return new TreeNode<>(p.hash, p.key, p.value, next);
  3. }

普通节点转换为树节点

替换


  
  1. public boolean replace(K key, V oldValue, V newValue) {
  2.     Node<K,V> e; V v;
  3.     // 根据key查找节点,如果节点的值不为oldValue时放弃修改
  4.     if ((e = getNode(hash(key), key)) != null &&
  5.             ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
  6.         e.value = newValue;
  7.         afterNodeAccess(e);
  8.         return true;
  9.     }
  10.     return false;
  11. }

只有在key存在,并且key对应的值跟oldValue一样时,才会替换


  
  1. public V replace(K key, V value) {
  2.     Node<K,V> e;
  3.     // 根据key查找节点
  4.     if ((e = getNode(hash(key), key)) != null) {
  5.         V oldValue = e.value;
  6.         // 新值覆盖原值
  7.         e.value = value;
  8.         afterNodeAccess(e);
  9.         // 返回原值
  10.         return oldValue;
  11.     }
  12.     return null;
  13. }

找到节点就覆盖,找不到就返回空

重要内部类
Node类

static class Node<K,V> implements Map.Entry<K,V>

Node属性


  
  1. // hash值
  2. final int hash;
  3. // 键
  4. final K key;
  5. // 值
  6. V value;
  7. // 下一个元素
  8. Node<K,V> next;

Node构造函数


  
  1. Node( int hash, K key, V value, Node<K,V> next) {
  2.     this.hash = hash;
  3.     this.key = key;
  4.     this.value = value;
  5.     this.next = next;
  6. }

Node构造函数只负责初始化内部属性

Node方法


  
  1. public final K getKey()        { return key; }
  2. public final V getValue()      { return value; }
  3. public final String toString() { return key + "=" + value; }
  4. public final V setValue(V newValue) {
  5.     V oldValue = value;
  6.     value = newValue;
  7.     return oldValue;
  8. }

可以看出只能更改值不能更改键


  
  1. public final int hashCode() {
  2.     return Objects.hashCode(key) ^ Objects.hashCode(value);
  3. }

hashcode方法比较特殊,键的hashcode和值的hashcode异或


  
  1. public final boolean equals(Object o) {
  2.     if (o == this)
  3.         return true;
  4.     if (o instanceof Map.Entry) {
  5.         Map.Entry<?,?> e = (Map.Entry<?,?>)o;
  6.         if (Objects.equals(key, e.getKey()) &&
  7.                 Objects.equals(value, e.getValue()))
  8.             return true;
  9.     }
  10.     return false;
  11. }

equals方法其实就是比较键和值是否一样

TreeNode类

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V>

不理解红黑树,看TreeNode源码比较吃力,这里顺带介绍下红黑树。
红黑树的五大特性:


  
  1. 1、每个结点是黑色或者红色。
  2. 2、根结点是黑色。
  3. 3、每个叶子结点( NIL)是黑色。(这里叶子结点,是指为空( NIL或NULL)的叶子结点!)
  4. 4、如果一个结点是红色的,则它的子结点必须是黑色的。
  5. 5、每个结点到叶子结点 NIL所经过的黑色结点的个数一样的。(确保没有一条路径会比
  6. 其他路径长出俩倍,所以红黑树是相对接近平衡的二叉树的!)

红黑树基本操作:


  
  1. 左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,
  2. 右子结点的左子结点变为旋转结点的右子结点,其左子结点保持不变。
  3. 右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,
  4. 左子结点的右子结点变为旋转结点的左子结点,其右子结点保持不变。
  5. 变色:结点的颜色由红变黑或由黑变红。

LinkedHashMap.Entry类


  
  1. static class Entry<K,V> extends HashMap.Node<K,V> {
  2.     Entry<K,V> before, after;
  3.     // 调用HashMap.Node构造函数
  4.     Entry( int hash, K key, V value, Node<K,V> next) {
  5.         super(hash, key, value, next);
  6.     }
  7. }

HashMap.Node前面已经讲过了,往上翻

TreeNode属性


  
  1. // 父节点
  2. TreeNode<K,V> parent; 
  3. // 左节点
  4. TreeNode<K,V> left;
  5. // 又结点
  6. TreeNode<K,V> right;
  7. // 前面结点
  8. TreeNode<K,V> prev;
  9. // 当前结点颜色
  10. boolean red;
  11. // LinkedHashMap.Entry中上一个元素(双向链表)
  12. Entry<K,V> before; 
  13. // LinkedHashMap.Entry中下一个元素(双向链表)
  14. Entry<K,V> after;
  15. // hash值
  16. final int hash;
  17. // 键
  18. final K key;
  19. // 值
  20. V value;
  21. // 下一个元素
  22. Node<K,V> next;

TreeNode构造函数


  
  1. TreeNode( int hash, K key, V val, Node<K,V> next) {
  2.     // 调用LinkedHashMap.Entry中构造函数
  3.     super(hash, key, val, next);
  4. }

TreeNode查找root结点


  
  1. final TreeNode<K,V> root() {
  2.     // 这里声明两个变量r、p,当前结点赋值给r,然后迭代
  3.     for (TreeNode<K,V> r = this, p;;) {
  4.         // 取当前结点的父节点赋值p,判断是否为空
  5.         if ((p = r.parent) == null)
  6.             // 没有父节点就是root结点
  7.             return r;
  8.         // p赋值给r继续迭代
  9.         r = p;
  10.     }
  11. }

TreeNode的find方法


  
  1. // h 为键的hash值,k 为键,kc 为比较器(实现了Comparable接口)
  2. final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
  3.     // 声明变量p,并把当前节点赋值给它
  4.     TreeNode<K,V> p = this;
  5.     do {
  6.         // 声明ph存放临时节点hash,dir 临时比较的结果,pk临时节点键
  7.         int ph, dir; K pk;
  8.         // pl 左节点,pr右节点,q下轮要找的节点
  9.         TreeNode<K,V> pl = p.left, pr = p.right, q;
  10.         // 把当前节点hash赋值给ph,判断当前节点hash是否大于h
  11.         if ((ph = p.hash) > h)
  12.             // 大于说明要找的节点在左边
  13.             p = pl;
  14.         else if (ph < h)
  15.             // ph 小于h,说明要找的在右边
  16.             p = pr;
  17.         // 能走到这,至少说明hash一样了
  18.         else if ((pk = p.key) == k || (k != null && k.equals(pk)))
  19.             // 把当前节点键赋值给pk,然后判断是否一样,一样直接就返回
  20.             return p;
  21.         // 能走这儿,说明hash一样key不一样
  22.         else if (pl == null)
  23.             // 左边为空赋值右边,希望寄托下一轮
  24.             p = pr;
  25.         else if (pr == null)
  26.             // 右边边为空赋值左边,希望寄托下一轮
  27.             p = pl;
  28.         // 能走这儿,说明hash一样,key不一样,还都有子节点
  29.         // comparableClassFor 获取键的比较器
  30.         // compareComparables 获取比较结果
  31.         // 判断比较器是否为空,为空获取k的比较器,然后比较大小
  32.         else if ((kc != null ||
  33.                 (kc = comparableClassFor(k)) != null) &&
  34.                 (dir = compareComparables(kc, k, pk)) != 0)
  35.             // 比较结果小于0走左边,大于零走右边,等于0还是左右不分
  36.             p = (dir < 0) ? pl : pr;
  37.         // 前面比较器比较结果也一样,尝试右边查一把
  38.         else if ((q = pr.find(h, k, kc)) != null)
  39.             // 查到了就返回
  40.             return q;
  41.         else
  42.             // 没查到,从左边继续下一轮查
  43.             p = pl;
  44.     } while (p != null);
  45.     // 都查完了还是空,只能返回没查到
  46.     return null;
  47. }

find方法使用比较多所以这里先做分析

TreeNode的getTreeNode方法


  
  1. final TreeNode<K,V> getTreeNode(int h, Object k) {
  2.     return ((parent != null) ? root() : this).find(h, k, null);
  3. }

这里先判断自己是不是root结点,如果是就从自身开始找,如果不是先找root,然后从root开始找TreeNode添加


  
  1. final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
  2.                                 int h, K k, V v) {
  3.     // 比较器
  4.     Class<?> kc = null;
  5.     // 是否搜索过(只有在hash和比较器都不能确定时才会用)
  6.     boolean searched = false;
  7.     // 获取头结点
  8.     TreeNode<K,V> root = (parent != null) ? root() : this;
  9.     // 从头结点开始遍历
  10.     for (TreeNode<K,V> p = root;;) {
  11.         // dir 比较器比较结果,ph 当前临时hash,pk当前临时键
  12.         int dir, ph; K pk;
  13.         if ((ph = p.hash) > h)
  14.             // 当前hash大于h,走左边
  15.             dir = - 1;
  16.         else if (ph < h)
  17.             // 当前hash小于h,走右边
  18.             dir = 1;
  19.         else if ((pk = p.key) == k || (k != null && k.equals(pk)))
  20.             // 键已存在
  21.             return p;
  22.         // 相同hash,键不等,通过比较器确定
  23.         else if ((kc == null &&
  24.                 (kc = comparableClassFor(k)) == null) ||
  25.                 (dir = compareComparables(kc, k, pk)) == 0) {
  26.             // 比较器比较也一样
  27.             if (!searched) { // 没搜索过
  28.                 TreeNode<K,V> q, ch;
  29.                 // 设置搜索标志
  30.                 searched = true;
  31.                 // 先搜索左边,没搜到,在搜索右边(find方法前面有将)
  32.                 if (((ch = p.left) != null &&
  33.                         (q = ch.find(h, k, kc)) != null) ||
  34.                         ((ch = p.right) != null &&
  35.                                 (q = ch.find(h, k, kc)) != null))
  36.                     // 搜索到就返回q
  37.                     return q;
  38.             }
  39.             // 搜索过或者没搜到,只能通过物理hash大小确定左右
  40.             dir = tieBreakOrder(k, pk);
  41.         }
  42.         // 走这儿说明没有重复建
  43.         TreeNode<K,V> xp = p;
  44.         // 这里一定可以确定左右(dir只能是-1或1,为0前面已经转掉了)
  45.         if ((p = (dir <= 0) ? p.left : p.right) == null) {
  46.             // 能到这儿,说明有一边为空,
  47.             Node<K,V> xpn = xp.next; // 兼容双向原链表结构
  48.             // 创建新树节点,原下级链表接在此节点后
  49.             TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
  50.             // 新节点上树
  51.             if (dir <= 0)
  52.                 xp.left = x;
  53.             else
  54.                 xp.right = x;
  55.             // 维护双向链表下级节点
  56.             xp.next = x;
  57.             // 维护树父级节点,维护双向链表前面结点
  58.             x.parent = x.prev = xp;
  59.             // 当前结点下级几点不为空时,需要把下级节点前面指向x
  60.             if (xpn != null)
  61.                 ((TreeNode<K,V>)xpn).prev = x;
  62.             // 平衡红黑树
  63.             moveRootToFront(tab, balanceInsertion(root, x));
  64.             // 添加成功返回空
  65.             return null;
  66.         }
  67.     }
  68. }

TreeNode删除


  
  1. // 此方法只有HashMap删除时找到应该删除的结点为树节点是才会调用
  2. final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
  3.                           boolean movable) {
  4.     int n;
  5.     // 数组为空时直接返回
  6.     if (tab == null || (n = tab.length) == 0)
  7.         return;
  8.     // 确定当前元素所在的槽
  9.     int index = (n - 1) & hash;
  10.     // first、root临时当前槽头节点
  11.     TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
  12.     // succ临时当前节点的下级节点, pred临时当前节点前面节点
  13.     TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
  14.     // 删除的节点是头节点
  15.     if (pred == null)
  16.         tab[index] = first = succ;
  17.     else
  18.         // 不是头节点,上级节点下级指向当前下级
  19.         pred.next = succ;
  20.     // 不是尾节点,把下级节点的上级指向当前上级
  21.     if (succ != null)
  22.         succ.prev = pred;
  23.     // --走到这双向链表中该节点已经删除成功--
  24.     // 头节点为空直接返回
  25.     if (first == null)
  26.         return;
  27.     // 头节点上级不为空
  28.     if (root.parent != null)
  29.         // 重新找root节点
  30.         root = root.root();
  31.     if (root == null // 头节点为空
  32.             || (movable
  33.             && (root.right == null // 头节点右为空
  34.             || (rl = root.left) == null // 头节点左为空
  35.             || rl.left == null))) { // 左节点的左节点为空
  36.         // 树链表,树转链表依赖双向链表,不依赖树结构(这种情况无视长度小于6?)
  37.         tab[index] = first.untreeify(map);   // too small
  38.         return;
  39.     }
  40.     // 下面开始从树结构中移除当前元素
  41.     TreeNode<K,V> p = this, pl = left, pr = right, replacement;
  42.     // 左右都不为空
  43.     if (pl != null && pr != null) {
  44.         // 找到右节点下最左边节点(循环左边的左边)
  45.         // 右边所有子节点中,最左边的一定最接近当前节点(可以自己推)
  46.         TreeNode<K,V> s = pr, sl;
  47.         while ((sl = s.left) != null) // find successor
  48.             s = sl;
  49.         // 交换p(当前节点)和s(右边最左下)的颜色
  50.         boolean c = s.red; s.red = p.red; p.red = c; // swap colors
  51.         TreeNode<K,V> sr = s.right;
  52.         TreeNode<K,V> pp = p.parent;
  53.         // 当前节点的右节点没有左节点(简单交换位置)
  54.         if (s == pr) {
  55.             // 当前上级指向s
  56.             p.parent = s;
  57.             // 当前节点放到s右边
  58.             s.right = p;
  59.         }
  60.         else {
  61.             // s节点和当前节点互换位置并设置父节点
  62.             TreeNode<K,V> sp = s.parent;
  63.             if ((p.parent = sp) != null) {
  64.                 if (s == sp.left)
  65.                     sp.left = p;
  66.                 else
  67.                     sp.right = p;
  68.             }
  69.             if ((s.right = pr) != null)
  70.                 pr.parent = s;
  71.         }
  72.         p.left = null;
  73.         // 如果s有右节点,把p设置成sr的父节点
  74.         if ((p.right = sr) != null)
  75.             sr.parent = p;
  76.         // 把p的左节点交接给s
  77.         if ((s.left = pl) != null)
  78.             pl.parent = s;
  79.         // 把p的父节点交接给s
  80.         if ((s.parent = pp) == null)
  81.             root = s;
  82.         else if (p == pp.left)
  83.             pp.left = s;
  84.         else
  85.             pp.right = s;
  86.         // 如果sr不为null替换p的节点就是sr,否则为p
  87.         if (sr != null)
  88.             replacement = sr;
  89.         else
  90.             replacement = p;
  91.     }
  92.     // 左右空的情况
  93.     else if (pl != null)
  94.         replacement = pl;
  95.     else if (pr != null)
  96.         replacement = pr;
  97.     else
  98.         replacement = p;
  99.     if (replacement != p) {
  100.         // 把要删除的移除掉
  101.         TreeNode<K,V> pp = replacement.parent = p.parent;
  102.         if (pp == null)
  103.             root = replacement;
  104.         else if (p == pp.left)
  105.             pp.left = replacement;
  106.         else
  107.             pp.right = replacement;
  108.         p.left = p.right = p.parent = null;
  109.     }
  110.     TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);
  111.     if (replacement == p) {   // detach
  112.         // 移除当前节点
  113.         TreeNode<K,V> pp = p.parent;
  114.         p.parent = null;
  115.         if (pp != null) {
  116.             if (p == pp.left)
  117.                 pp.left = null;
  118.             else if (p == pp.right)
  119.                 pp.right = null;
  120.         }
  121.     }
  122.     // 是否需要平衡树
  123.     if (movable)
  124.         moveRootToFront(tab, r);
  125. }

树转链表


  
  1. final Node<K,V> untreeify(HashMap<K,V> map) {
  2.     Node<K,V> hd = null, tl = null;
  3.     // 这里只依赖链表转换
  4.     for (Node<K,V> q = this; q != null; q = q.next) {
  5.         // TreeNode将转化成Node
  6.         Node<K,V> p = map.replacementNode(q, null);
  7.         if (tl == null)
  8.             hd = p;
  9.         else
  10.             tl.next = p;
  11.         tl = p;
  12.     }
  13.     return hd;
  14. }

TreeNode扩容迁移节点


  
  1. final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
  2.     TreeNode<K,V> b = this;
  3.     // loHead 扩容前头节点,loTail扩容前临时结点
  4.     // hiHead 扩容前头节点,hiTail扩容前临时结点
  5.     TreeNode<K,V> loHead = null, loTail = null;
  6.     TreeNode<K,V> hiHead = null, hiTail = null;
  7.     // lc原槽中元素个数, hc扩容槽元素个数
  8.     int lc = 0, hc = 0;
  9.     for (TreeNode<K,V> e = b, next; e != null; e = next) {
  10.         next = (TreeNode<K,V>)e.next;
  11.         e.next = null;
  12.         if ((e.hash & bit) == 0) {
  13.             if ((e.prev = loTail) == null)
  14.                 loHead = e;
  15.             else
  16.                 loTail.next = e;
  17.             loTail = e;
  18.             ++lc;
  19.         }
  20.         else {
  21.             if ((e.prev = hiTail) == null)
  22.                 hiHead = e;
  23.             else
  24.                 hiTail.next = e;
  25.             hiTail = e;
  26.             ++hc;
  27.         }
  28.     }
  29.     if (loHead != null) {
  30.         // 原槽元素个数小于等于6,树转链表
  31.         if (lc <= UNTREEIFY_THRESHOLD)
  32.             tab[index] = loHead.untreeify(map);
  33.         else {
  34.             tab[index] = loHead;
  35.             if (hiHead != null)
  36.                 loHead.treeify(tab);
  37.         }
  38.     }
  39.     if (hiHead != null) {
  40.         // 扩容槽中元素个数小于等于6,树转链表
  41.         if (hc <= UNTREEIFY_THRESHOLD)
  42.             tab[index + bit] = hiHead.untreeify(map);
  43.         else {
  44.             tab[index + bit] = hiHead;
  45.             if (loHead != null)
  46.                 hiHead.treeify(tab);
  47.         }
  48.     }
  49. }

双向链表转树
调用此方法前必须先构建双向链表


  
  1. final void treeify(Node<K,V>[] tab) {
  2.     TreeNode<K,V> root = null;
  3.     for (TreeNode<K,V> x = this, next; x != null; x = next) {
  4.         next = (TreeNode<K,V>)x.next;
  5.         x.left = x.right = null;
  6.         // 插入的是第一个元素 并给root赋值
  7.         if (root == null) {
  8.             x.parent = null;
  9.             x.red = false;
  10.             root = x;
  11.         }
  12.         else {
  13.             K k = x.key;
  14.             int h = x.hash;
  15.             Class<?> kc = null;
  16.             //插入到红黑树中的位置 逻辑跟putTreeVal类似
  17.             for (TreeNode<K,V> p = root;;) {
  18.                 int dir, ph;
  19.                 K pk = p.key;
  20.                 if ((ph = p.hash) > h)
  21.                     dir = - 1;
  22.                 else if (ph < h)
  23.                     dir = 1;
  24.                 else if ((kc == null &&
  25.                         (kc = comparableClassFor(k)) == null) ||
  26.                         (dir = compareComparables(kc, k, pk)) == 0)
  27.                     dir = tieBreakOrder(k, pk);
  28.                 TreeNode<K,V> xp = p;
  29.                 if ((p = (dir <= 0) ? p.left : p.right) == null) {
  30.                     x.parent = xp;
  31.                     if (dir <= 0)
  32.                         xp.left = x;
  33.                     else
  34.                         xp.right = x;
  35.                     root = balanceInsertion(root, x);
  36.                     break;
  37.                 }
  38.             }
  39.         }
  40.     }
  41.     // 把root节点移到链表头
  42.     moveRootToFront(tab, root);
  43. }

头节点前移


  
  1. static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
  2.     int n;
  3.     if (root != null && tab != null && (n = tab.length) > 0) {
  4.         // 确定槽位置
  5.         int index = (n - 1) & root.hash;
  6.         TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
  7.         // 如果链表的头不是红黑树的头节点 则把root移到头节点 也就是first的前面
  8.         if (root != first) {
  9.             Node<K,V> rn;
  10.             tab[index] = root;
  11.             TreeNode<K,V> rp = root.prev;
  12.             if ((rn = root.next) != null)
  13.                 ((TreeNode<K,V>)rn).prev = rp;
  14.             if (rp != null)
  15.                 rp.next = rn;
  16.             if (first != null)
  17.                 first.prev = root;
  18.             root.next = first;
  19.             root.prev = null;
  20.         }
  21.         // 检查一下红黑树的各个成员变量是否正常
  22.         assert checkInvariants(root);
  23.     }
  24. }

moveRootToFront的作用就是把root节点move到链表的头.


转载:https://blog.csdn.net/nan8426/article/details/72898204
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场