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

Node静态内部类
static class Node<K,V> implements Map.Entry<K,V> {//1.Node<K,V>总共有四个成员变量,其中hash为扰动后的hash,查看put方法了解//2.Map.Entry<K,V>表示Map接口内部的Entry接口,即接口里面有接口final int hash;final K key;V value;Node<K,V> next;Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}public final K getKey() { return key; }public final V getValue() { return value; }public final String toString() { return key + "=" + value; }//3.果然重写equals就得重写hashCodepublic final int hashCode() {return Objects.hashCode(key) ^ Objects.hashCode(value);}//4.设置新值,返回旧值public final V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue;}public final boolean equals(Object o) {if (o == this)return true;if (o instanceof Map.Entry) {Map.Entry<?,?> e = (Map.Entry<?,?>)o;if (Objects.equals(key, e.getKey()) &&Objects.equals(value, e.getValue()))return true;}return false;}}
Object类的hashCode和equals方法
public static int hashCode(Object o) {return o != null ? o.hashCode() : 0;}public native int hashCode();这里暂时理解为将对象在内存中的地址值映射为一个hash值,并保证全局唯一。所以我们可以理解为Object中hashcode返回的值就是“地址值”
public boolean equals(Object obj) {return (this == obj);}==默认比较的就是地址值
无参构造HashMap()
//Constructs an empty HashMap with the default initial capacity (16)//and the default load factor (0.75).public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}
static final float DEFAULT_LOAD_FACTOR = 0.75f;//调用无参构造,此时的默认加载因子是0.75,那默认的初始容量在哪里设置为了16?//应该是在第一次put的时候才会进行设置,避免内存空间的浪费。
HashMap的成员变量
//The default initial capacity - MUST be a power of two.static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16//The maximum capacity,//used if a higher value is implicitly specified by either of the constructors with arguments.//MUST be a power of two <= 1<<30.static final int MAXIMUM_CAPACITY = 1 << 30;//The load factor used when none specified in constructor.static final float DEFAULT_LOAD_FACTOR = 0.75f;//这个应该就是转红黑树的条件static final int TREEIFY_THRESHOLD = 8;//这个应该就是红黑树再转成链表的条件static final int UNTREEIFY_THRESHOLD = 6;//这个应该就是哈希表中的节点数目达到64就会进行链表转红黑树的操作static final int MIN_TREEIFY_CAPACITY = 64;//这个就是我们说的散列表啊,即Node类型的数组transient Node<K,V>[] table;//这个在Hashmap的遍历中使用transient Set<Map.Entry<K,V>> entrySet;//这个是哈希表中的键值对的数目transient int size;//这个就是对应并发修改异常ConcurrentModification Exception//改变键值对的数目,或者进行rehash都会进行加一transient int modCount;//The next size value at which to resize (capacity * load factor).//我的感觉就是当哈希表的节点数目达到capacity * load factor就会进行扩容int threshold;//当哈希表中的节点数目达到capacity * load factor时就会扩容//为什么?//我的理解是比如初始容量是16,那么当节点数达到12时,剩下的没有被使用的位置已经很少了,//再继续添加的话容易发生哈希冲突,一旦发生哈希冲突的话就得将链表变长,这样会降低查询效率,//所以这个时候扩容的话每个链表的长度都能得到控制,也就是以空间换时间final float loadFactor;
put方法
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}//key通过hashcode算出哈希值后,会经过一个扰动函数hash()生成扰动后的哈希值//扰动的具体操作:将初始哈希值向右移动16位后高16位补零再与初始哈希值进行异或//这样的目的是为了哈希值更散列。。。。//所以hash()的返回值是扰动后的哈希值hash:key扰动之后的哈希值key:put传入的keyvalue:put传入的valueonlyIfAbsent:为真时则不会覆盖final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {//1.tab:当前的散列表,即Node<K,V>类型的数组//2.p:当前散列表的某个元素//3.n:当前散列表的数组的长度//4.i:路由寻址结果//5.table:HashMap成员变量,表示散列表,即new HashMap()得到的table是没有进行初始化的,故起始为nullNode<K,V>[] tab; Node<K,V> p; int n, i;//6.延迟初始化,第一次调用putVal时会初始化HashMap中最耗费内存的散列表(顾名思义,HashMap对象中还有其他属性,比如threshold,loadFactor等)//7.这里的tab是在方法中定义的,是如何影响成员属性table的??这就与resize()函数相关,该后续后面再讲if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//8:当前索引值对应的数组元素为空(术语称为桶位为空),说明该索引位置尚未赋值,因此直接将当前key-value封装成Node对象填充该位置if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);//9:当前索引值对应的数组元素不为空时else {//10:e和k都是临时变量Node<K,V> e; K k;//11:p的取值来自于//8,即if语句中也能赋值(无论if语句成立与否)//12:当前索引到的元素的hash值等于传入的哈希值,且两者的key也一致(这里用两个条件相或,我只能认为是优化,即第一个条件是当传入的key就是散列表中key时就短路输出),此时就要准备进行覆盖操作(当onlyIfAbsent为false时才覆盖)if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;//13:p如果是树节点(红黑树),那就得进行红黑树操作else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//14:p为单链表else {for (int binCount = 0; ; ++binCount) {//15:(e = p.next) == null成立时(因为for循环和p=e),p表示单链表最后一个元素,所以此时就是将p后面新接一个节点if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);//16:如果数组中的元素表示头结点,则后续再接上7+1=8个时就会进行树化,//如果加上头结点,则是整个链表如果达到9个就会进行树化if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}//17:当在链表遍历的过程中找到了哈希值相同的Node节点,//且传入的key与该节点中的key也相同,就要准备进行覆盖操作//注意:每一个node节点都有四个属性,即hash,key,value,nextif (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}//18:这里就是上面多处提及的覆盖操作//个人认为之所以覆盖操作放到这里是因为链表的头结点的key值重复时需要进行覆盖,//而且当链表中间的节点的key值重复时也需要覆盖,所以就统一放到最后进行覆盖if (e != null) { // existing mapping for key//19:取出Node的valueV oldValue = e.value;//20:onlYIfAbsent允许覆盖时就可进行覆盖,将传入的value覆盖原来的valueif (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);//21:返回原来的valuereturn oldValue;}}//22:modCount:散列表结构变化的次数,除了覆盖操作,其他都算结构变化,//因此只有覆盖操作那里才会提前return++modCount;//23:size:当前散列表中的节点个数,当其大于扩容阈值threshold就会触发扩容resizeif (++size > threshold)resize();afterNodeInsertion(evict);return null;}
resize方法
final Node<K,V>[] resize() {//1.oldTab:扩容前的哈希表Node<K,V>[] oldTab = table;//2.oldCap:扩容前哈希表的长度,即对应数组的长度int oldCap = (oldTab == null) ? 0 : oldTab.length;//3:扩容前的扩容阈值,即触发本次扩容的扩容阈值int oldThr = threshold;//4:扩容后的数组长度,扩容后的扩容阈值int newCap, newThr = 0;//5:oldCap大于零,说明HashMap中的散列表已经初始化过,//是一次正常扩容(HashMap是类,类中有很多属性和方法,其中一个属性就是散列表)if (oldCap > 0) {//6:如果扩容前数组长度大于等于最大容量,说明此时无法扩容,//就将threshod设置为int最大值(这样之后就不会在putVal中进入resize//23),返回旧表.if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}//7:老长度乘以2之后赋值给新长度,其值小于最大容量,//且老长度大于等于默认初始容量(容量就是数组长度),就将扩容阈值也翻倍else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}//8:oldCap等于零,说明HashMap中的散列表是null,尚未初始化,//此时四个构造方法中的三个构造方法会使得oldThr的值大于零//就是说人为设置了初始容量!!else if (oldThr > 0) // initial capacity was placed in threshold//9:oldThr来自于threshold,而构造方法中的threshold都是tableSizeFor的返回值,//即为2的幂次方//10:threshold就是散列表中的节点个数的扩容最值,//即散列表中的节点个数达到这个值就要进行扩容newCap = oldThr;//10:调用四个构造方法中的new HashMap()时,oldCap和oldThr都为0,//此时newCap=16,newThr=16*0.75=12else { // zero initial threshold signifies using defaultsnewCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}//11:两种情况newThr等于零(即前面newThr没有赋值的情况)://7的判断条件不满足时(人为设置初始容量为一个小于16的数),//8的判断条件满足时;if (newThr == 0) {//12:依旧还是容量乘以负载因子,然后由于<条件绝大多数情况都满足,所以ft取整之后赋值给newThrfloat ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}//13:将newThr赋值给当前HashMap对象的thresholdthreshold = newThr;//14:到目前为止我们计算出了扩容后的容量和扩容阈值,下面就要根据它们进行扩容的具体操作@SuppressWarnings({"rawtypes","unchecked"})//15:创建出一个更长更大的数组Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab;//16:oldTab不为null,则说明扩容前数组中有值if (oldTab != null) {//17:挨个数组的桶位(索引)for (int j = 0; j < oldCap; ++j) {Node<K,V> e;//18:当前桶位不为null,则分为单个数据,链表,红黑树三种情况,分别对应下面的if else if else语句if ((e = oldTab[j]) != null) {//19:方便JVM GC时回收内存oldTab[j] = null;//20:单个数据时利用路由算法算出该节点在新的散列表中的索引值,然后将该Node数据填充到新的散列表中if (e.next == null)newTab[e.hash & (newCap - 1)] = e;//21:红黑树情况先不讲else if (e instanceof TreeNode)((TreeNode<K,V>)e).split(this, newTab, j, oldCap);//22:链表时:这个先看下面的图://23:看完图之后再看代码else { // preserve order//lo*表示低位链表,其中head表示头部,tail表示尾部,低位链表增加节点的过程中head不变,tail在变Node<K,V> loHead = null, loTail = null;//hi*表示高位链表,其中head表示头部,tail表示尾部,低位链表增加节点的过程中head不变,tail在变Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;do {//next=e.next用来遍历链表next = e.next;//e.hash & oldCap用来判断从低位开始第5位(以容量16扩展为32为例)是否为0,为0的话就将该节点放进低位链表if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}//为1的话就将该节点放进高位链表else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);//最后将低位链表和高位链表放到新的散列表的不同索引(桶位)处if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab;}
