HashMap本身也是线程不安全的
它的底层是数组加链表
具体来说就是一个Node类型的数组
即数组的每个元素是Node类型
然后Node类型就可以利用next和value属性组成链表

image.png

Node静态内部类

  1. static class Node<K,V> implements Map.Entry<K,V> {
  2. //1.Node<K,V>总共有四个成员变量,其中hash为扰动后的hash,查看put方法了解
  3. //2.Map.Entry<K,V>表示Map接口内部的Entry接口,即接口里面有接口
  4. final int hash;
  5. final K key;
  6. V value;
  7. Node<K,V> next;
  8. Node(int hash, K key, V value, Node<K,V> next) {
  9. this.hash = hash;
  10. this.key = key;
  11. this.value = value;
  12. this.next = next;
  13. }
  14. public final K getKey() { return key; }
  15. public final V getValue() { return value; }
  16. public final String toString() { return key + "=" + value; }
  17. //3.果然重写equals就得重写hashCode
  18. public final int hashCode() {
  19. return Objects.hashCode(key) ^ Objects.hashCode(value);
  20. }
  21. //4.设置新值,返回旧值
  22. public final V setValue(V newValue) {
  23. V oldValue = value;
  24. value = newValue;
  25. return oldValue;
  26. }
  27. public final boolean equals(Object o) {
  28. if (o == this)
  29. return true;
  30. if (o instanceof Map.Entry) {
  31. Map.Entry<?,?> e = (Map.Entry<?,?>)o;
  32. if (Objects.equals(key, e.getKey()) &&
  33. Objects.equals(value, e.getValue()))
  34. return true;
  35. }
  36. return false;
  37. }
  38. }

Object类的hashCode和equals方法

  1. public static int hashCode(Object o) {
  2. return o != null ? o.hashCode() : 0;
  3. }
  4. public native int hashCode();
  5. 这里暂时理解为将对象在内存中的地址值映射为一个hash值,并保证全局唯一。
  6. 所以我们可以理解为Objecthashcode返回的值就是“地址值”
  1. public boolean equals(Object obj) {
  2. return (this == obj);
  3. }
  4. ==默认比较的就是地址值

无参构造HashMap()

  1. //Constructs an empty HashMap with the default initial capacity (16)
  2. //and the default load factor (0.75).
  3. public HashMap() {
  4. this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  5. }
  1. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  2. //调用无参构造,此时的默认加载因子是0.75,那默认的初始容量在哪里设置为了16?
  3. //应该是在第一次put的时候才会进行设置,避免内存空间的浪费。

HashMap的成员变量

  1. //The default initial capacity - MUST be a power of two.
  2. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  3. //The maximum capacity,
  4. //used if a higher value is implicitly specified by either of the constructors with arguments.
  5. //MUST be a power of two <= 1<<30.
  6. static final int MAXIMUM_CAPACITY = 1 << 30;
  7. //The load factor used when none specified in constructor.
  8. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  9. //这个应该就是转红黑树的条件
  10. static final int TREEIFY_THRESHOLD = 8;
  11. //这个应该就是红黑树再转成链表的条件
  12. static final int UNTREEIFY_THRESHOLD = 6;
  13. //这个应该就是哈希表中的节点数目达到64就会进行链表转红黑树的操作
  14. static final int MIN_TREEIFY_CAPACITY = 64;
  15. //这个就是我们说的散列表啊,即Node类型的数组
  16. transient Node<K,V>[] table;
  17. //这个在Hashmap的遍历中使用
  18. transient Set<Map.Entry<K,V>> entrySet;
  19. //这个是哈希表中的键值对的数目
  20. transient int size;
  21. //这个就是对应并发修改异常ConcurrentModification Exception
  22. //改变键值对的数目,或者进行rehash都会进行加一
  23. transient int modCount;
  24. //The next size value at which to resize (capacity * load factor).
  25. //我的感觉就是当哈希表的节点数目达到capacity * load factor就会进行扩容
  26. int threshold;
  27. //当哈希表中的节点数目达到capacity * load factor时就会扩容
  28. //为什么?
  29. //我的理解是比如初始容量是16,那么当节点数达到12时,剩下的没有被使用的位置已经很少了,
  30. //再继续添加的话容易发生哈希冲突,一旦发生哈希冲突的话就得将链表变长,这样会降低查询效率,
  31. //所以这个时候扩容的话每个链表的长度都能得到控制,也就是以空间换时间
  32. final float loadFactor;

put方法

  1. public V put(K key, V value) {
  2. return putVal(hash(key), key, value, false, true);
  3. }
  4. //key通过hashcode算出哈希值后,会经过一个扰动函数hash()生成扰动后的哈希值
  5. //扰动的具体操作:将初始哈希值向右移动16位后高16位补零再与初始哈希值进行异或
  6. //这样的目的是为了哈希值更散列。。。。
  7. //所以hash()的返回值是扰动后的哈希值
  8. hashkey扰动之后的哈希值
  9. keyput传入的key
  10. valueput传入的value
  11. onlyIfAbsent:为真时则不会覆盖
  12. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  13. boolean evict) {
  14. //1.tab:当前的散列表,即Node<K,V>类型的数组
  15. //2.p:当前散列表的某个元素
  16. //3.n:当前散列表的数组的长度
  17. //4.i:路由寻址结果
  18. //5.table:HashMap成员变量,表示散列表,即new HashMap()得到的table是没有进行初始化的,故起始为null
  19. Node<K,V>[] tab; Node<K,V> p; int n, i;
  20. //6.延迟初始化,第一次调用putVal时会初始化HashMap中最耗费内存的散列表(顾名思义,HashMap对象中还有其他属性,比如threshold,loadFactor等)
  21. //7.这里的tab是在方法中定义的,是如何影响成员属性table的??这就与resize()函数相关,该后续后面再讲
  22. if ((tab = table) == null || (n = tab.length) == 0)
  23. n = (tab = resize()).length;
  24. //8:当前索引值对应的数组元素为空(术语称为桶位为空),说明该索引位置尚未赋值,因此直接将当前key-value封装成Node对象填充该位置
  25. if ((p = tab[i = (n - 1) & hash]) == null)
  26. tab[i] = newNode(hash, key, value, null);
  27. //9:当前索引值对应的数组元素不为空时
  28. else {
  29. //10:e和k都是临时变量
  30. Node<K,V> e; K k;
  31. //11:p的取值来自于//8,即if语句中也能赋值(无论if语句成立与否)
  32. //12:当前索引到的元素的hash值等于传入的哈希值,且两者的key也一致(这里用两个条件相或,我只能认为是优化,即第一个条件是当传入的key就是散列表中key时就短路输出),此时就要准备进行覆盖操作(当onlyIfAbsent为false时才覆盖)
  33. if (p.hash == hash &&
  34. ((k = p.key) == key || (key != null && key.equals(k))))
  35. e = p;
  36. //13:p如果是树节点(红黑树),那就得进行红黑树操作
  37. else if (p instanceof TreeNode)
  38. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  39. //14:p为单链表
  40. else {
  41. for (int binCount = 0; ; ++binCount) {
  42. //15:(e = p.next) == null成立时(因为for循环和p=e),p表示单链表最后一个元素,所以此时就是将p后面新接一个节点
  43. if ((e = p.next) == null) {
  44. p.next = newNode(hash, key, value, null);
  45. //16:如果数组中的元素表示头结点,则后续再接上7+1=8个时就会进行树化,
  46. //如果加上头结点,则是整个链表如果达到9个就会进行树化
  47. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  48. treeifyBin(tab, hash);
  49. break;
  50. }
  51. //17:当在链表遍历的过程中找到了哈希值相同的Node节点,
  52. //且传入的key与该节点中的key也相同,就要准备进行覆盖操作
  53. //注意:每一个node节点都有四个属性,即hash,key,value,next
  54. if (e.hash == hash &&
  55. ((k = e.key) == key || (key != null && key.equals(k))))
  56. break;
  57. p = e;
  58. }
  59. }
  60. //18:这里就是上面多处提及的覆盖操作
  61. //个人认为之所以覆盖操作放到这里是因为链表的头结点的key值重复时需要进行覆盖,
  62. //而且当链表中间的节点的key值重复时也需要覆盖,所以就统一放到最后进行覆盖
  63. if (e != null) { // existing mapping for key
  64. //19:取出Node的value
  65. V oldValue = e.value;
  66. //20:onlYIfAbsent允许覆盖时就可进行覆盖,将传入的value覆盖原来的value
  67. if (!onlyIfAbsent || oldValue == null)
  68. e.value = value;
  69. afterNodeAccess(e);
  70. //21:返回原来的value
  71. return oldValue;
  72. }
  73. }
  74. //22:modCount:散列表结构变化的次数,除了覆盖操作,其他都算结构变化,
  75. //因此只有覆盖操作那里才会提前return
  76. ++modCount;
  77. //23:size:当前散列表中的节点个数,当其大于扩容阈值threshold就会触发扩容resize
  78. if (++size > threshold)
  79. resize();
  80. afterNodeInsertion(evict);
  81. return null;
  82. }

resize方法

  1. final Node<K,V>[] resize() {
  2. //1.oldTab:扩容前的哈希表
  3. Node<K,V>[] oldTab = table;
  4. //2.oldCap:扩容前哈希表的长度,即对应数组的长度
  5. int oldCap = (oldTab == null) ? 0 : oldTab.length;
  6. //3:扩容前的扩容阈值,即触发本次扩容的扩容阈值
  7. int oldThr = threshold;
  8. //4:扩容后的数组长度,扩容后的扩容阈值
  9. int newCap, newThr = 0;
  10. //5:oldCap大于零,说明HashMap中的散列表已经初始化过,
  11. //是一次正常扩容(HashMap是类,类中有很多属性和方法,其中一个属性就是散列表)
  12. if (oldCap > 0) {
  13. //6:如果扩容前数组长度大于等于最大容量,说明此时无法扩容,
  14. //就将threshod设置为int最大值(这样之后就不会在putVal中进入resize//23),返回旧表.
  15. if (oldCap >= MAXIMUM_CAPACITY) {
  16. threshold = Integer.MAX_VALUE;
  17. return oldTab;
  18. }
  19. //7:老长度乘以2之后赋值给新长度,其值小于最大容量,
  20. //且老长度大于等于默认初始容量(容量就是数组长度),就将扩容阈值也翻倍
  21. else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
  22. oldCap >= DEFAULT_INITIAL_CAPACITY)
  23. newThr = oldThr << 1; // double threshold
  24. }
  25. //8:oldCap等于零,说明HashMap中的散列表是null,尚未初始化,
  26. //此时四个构造方法中的三个构造方法会使得oldThr的值大于零
  27. //就是说人为设置了初始容量!!
  28. else if (oldThr > 0) // initial capacity was placed in threshold
  29. //9:oldThr来自于threshold,而构造方法中的threshold都是tableSizeFor的返回值,
  30. //即为2的幂次方
  31. //10:threshold就是散列表中的节点个数的扩容最值,
  32. //即散列表中的节点个数达到这个值就要进行扩容
  33. newCap = oldThr;
  34. //10:调用四个构造方法中的new HashMap()时,oldCap和oldThr都为0,
  35. //此时newCap=16,newThr=16*0.75=12
  36. else { // zero initial threshold signifies using defaults
  37. newCap = DEFAULT_INITIAL_CAPACITY;
  38. newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
  39. }
  40. //11:两种情况newThr等于零(即前面newThr没有赋值的情况)://7的判断条件不满足时(人为设置初始容量为一个小于16的数),//8的判断条件满足时;
  41. if (newThr == 0) {
  42. //12:依旧还是容量乘以负载因子,然后由于<条件绝大多数情况都满足,所以ft取整之后赋值给newThr
  43. float ft = (float)newCap * loadFactor;
  44. newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
  45. (int)ft : Integer.MAX_VALUE);
  46. }
  47. //13:将newThr赋值给当前HashMap对象的threshold
  48. threshold = newThr;
  49. //14:到目前为止我们计算出了扩容后的容量和扩容阈值,下面就要根据它们进行扩容的具体操作
  50. @SuppressWarnings({"rawtypes","unchecked"})
  51. //15:创建出一个更长更大的数组
  52. Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
  53. table = newTab;
  54. //16:oldTab不为null,则说明扩容前数组中有值
  55. if (oldTab != null) {
  56. //17:挨个数组的桶位(索引)
  57. for (int j = 0; j < oldCap; ++j) {
  58. Node<K,V> e;
  59. //18:当前桶位不为null,则分为单个数据,链表,红黑树三种情况,分别对应下面的if else if else语句
  60. if ((e = oldTab[j]) != null) {
  61. //19:方便JVM GC时回收内存
  62. oldTab[j] = null;
  63. //20:单个数据时利用路由算法算出该节点在新的散列表中的索引值,然后将该Node数据填充到新的散列表中
  64. if (e.next == null)
  65. newTab[e.hash & (newCap - 1)] = e;
  66. //21:红黑树情况先不讲
  67. else if (e instanceof TreeNode)
  68. ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
  69. //22:链表时:这个先看下面的图:
  70. //23:看完图之后再看代码
  71. else { // preserve order
  72. //lo*表示低位链表,其中head表示头部,tail表示尾部,低位链表增加节点的过程中head不变,tail在变
  73. Node<K,V> loHead = null, loTail = null;
  74. //hi*表示高位链表,其中head表示头部,tail表示尾部,低位链表增加节点的过程中head不变,tail在变
  75. Node<K,V> hiHead = null, hiTail = null;
  76. Node<K,V> next;
  77. do {
  78. //next=e.next用来遍历链表
  79. next = e.next;
  80. //e.hash & oldCap用来判断从低位开始第5位(以容量16扩展为32为例)是否为0,为0的话就将该节点放进低位链表
  81. if ((e.hash & oldCap) == 0) {
  82. if (loTail == null)
  83. loHead = e;
  84. else
  85. loTail.next = e;
  86. loTail = e;
  87. }
  88. //为1的话就将该节点放进高位链表
  89. else {
  90. if (hiTail == null)
  91. hiHead = e;
  92. else
  93. hiTail.next = e;
  94. hiTail = e;
  95. }
  96. } while ((e = next) != null);
  97. //最后将低位链表和高位链表放到新的散列表的不同索引(桶位)处
  98. if (loTail != null) {
  99. loTail.next = null;
  100. newTab[j] = loHead;
  101. }
  102. if (hiTail != null) {
  103. hiTail.next = null;
  104. newTab[j + oldCap] = hiHead;
  105. }
  106. }
  107. }
  108. }
  109. }
  110. return newTab;
  111. }