跟踪代码
以下面代码示例,跟踪具体做了什么
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();//第一步
map.put("张三", 14);//第二步
map.put("李四", 15);//第三步
map.put("重地",1 );//第四步
map.put("通话",1 );//第五步
}
第一步:初始化
初始化方法 new HashMap<>()
进来这个方法,但没有创建Node数组(put才会做),无符号右移 a <<< b ,快速计算公式: a ∗ 2 b a*2^b a∗2b
// 默认的初始容量是16 1 << 4 相当于 1*2的4次方
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
/**
* 构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空HashMap,size=0 。
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
第二步:第一次put
进入map.put("张三", 14)
,计算key:张三的 hashCode
key.hashCode() :0000 0000 0000 1011 1101 0010 1110 1001 十进制 774889
h >>> 16:0000 0000 0000 0000 0000 0000 0000 1011 = 0000 0000 0000 1011 1101 0010 1110 1001 >>> 16
^异或运算:相同为0,不同为1
0000 0000 0000 1011 1101 0010 1110 1001
^
0000 0000 0000 0000 0000 0000 0000 1011
0000 0000 0000 1011 1101 0010 1110 0010 十进制 774,882
这就是最后张三的hash值774,882
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
为什么要右移16位?为了更加散列,减少哈希冲突
由于和(length-1)运算,length 绝大多数情况小于2的16次方。所以始终是hashcode 的低16位(甚至更低)参与运算。要是高16位也参与运算,会让得到的下标更加散列。
所以这样高16位是用不到的,如何让高16也参与运算呢。所以才有hash(Object key)方法。让他的hashCode()和自己的高16位^运算。所以(h >>> 16)得到他的高16位与hashCode()进行^运算。
例子:在计算(n - 1) & hash 元素存放位置的时候,之前不右移16位的结果
随便拿个例子举例,比如高16位都是1
1111 1111 1111 1111 1101 0010 1110 1001 hash
&
0000 0000 0000 0000 0000 0000 0000 1111 n - 1 = 16-1= 15
0000 0000 0000 0000 0000 0000 0000 1001 9
再拿一个高位也有1的hash例子
1101 1001 1001 1001 1101 0010 1110 1001 hash
&
0000 0000 0000 0000 0000 0000 0000 1111 n - 1 = 16-1= 15
0000 0000 0000 0000 0000 0000 0000 1001 9
可以发现,高位无法参与到计算中,使得哈希冲突率提升,如果有右移16为让高位也参与计算,将减少哈希冲突,目的是为了数组长度很小的时候做的预备
如果当 n 即数组长度很小,假设是 16 的话,那么 n - 1 即为 1111 ,这样的值和 hashCode 直接做按位与操作,实际上只使用了哈希值的后 4 位。如果当哈希值的高位变化很大,低位变化很小,这样就很容易造成哈希冲突了,所以这里把高低位都利用起来,从而解决了这个问题。
为什么HashMap的长度为什么要是2的幂?
因为HashMap使用的方法很巧妙,它通过hash & (table.length -1 )来得到该对象的保存位,前面说过HashMap底层数组的长度总是2的n次方,这是HashMap在速度上的优化。当length总是2的n次方时,hash & (length-1 )运算等价于对length取模,也就是hash%length,但是&比%具有更高的效率。比如 n %32=n&(32-1)。
3.接着进入putVal(774,882, "张三", 14, false, true)
,接下来看代码注释
/**
该表在首次使用时初始化,并根据需要调整大小。
分配时,长度始终是 2 的幂。(我们还在某些操作中允许长度为零,以允许当前不需要的引导机制。)
*/
transient Node<K,V>[] table;
/**
此 HashMap 已在结构上修改的次数 结构修改是更改 HashMap 中的映射数量或以其他方式修改其内部结构(例如,重新散列)的那些。 该字段用于使 HashMap 的 Collection-views 上的迭代器快速失败。 (请参阅 ConcurrentModificationException)。
*/
transient int modCount;
/**
此映射中包含的键值映射的数量。
*/
transient int size;
static class Node<K,V> implements Map.Entry<K,V> {
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;
}
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//1.1 第一次进来 table 为空(上面那个),为ture
if ((tab = table) == null || (n = tab.length) == 0)
//1.2 跳转到 第一次:运行扩容方法 resize(),扩容后现在tab为16容量的数组,每个索引(桶)都是为null
n = (tab = resize()).length;//16
//1.13 开始计算元素放哪个桶(索引位置)
/*
i = (n - 1) & hash 表示计算数组的索引赋值给i,即确定元素存放在哪个桶中。
0000 0000 0000 0000 0000 0000 0000 1111 15(n - 1)
&
0000 0000 0000 1011 1101 0010 1110 0010 774,882(hash)
0000 0000 0000 0000 0000 0000 0000 0010 2
p = tab[i = (n - 1) & hash]表示获取计算出的位置的数据赋值给结点p
此时i=2,p=tab[2]=null
*/
if ((p = tab[i = (n - 1) & hash]) == null)
//创建一个新节点给桶tab[2]=node(张三,14),此时如下图1.0
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;
}
}
if (e != null) {
// existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//1.14 刚开始为0,加完为1
++modCount;
//1.15
//判断实际大小是否大于threshold阈值,如果超过则扩容;
//刚开始 size=0 > threshold=16,false
//执行完 size=1
if (++size > threshold)
resize();
//1.16
//插入后回调,LinkedHashMap中被覆盖的afterNodeInsertion方法,用来回调移除最早放入Map的对象,对HashMap毫无作用
afterNodeInsertion(evict);
//1.17
return null;
}
图1.0
第一次:运行扩容方法 resize()
这才是第一次初始化数组,初始化一个容量为16的Node数组
final Node<K,V>[] resize() {
//1.3
//第一次进来table,还是空
Node<K,V>[] oldTab = table;
//1.4
int oldCap = (oldTab == null) ? 0 : oldTab.length;//0
//1.5
//threshold刚开始为0
int oldThr = threshold;
//1.6
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1;
}
else if (oldThr > 0)
newCap = oldThr;
else {
//1.7
//0初始阈值表示使用默认值
newCap = DEFAULT_INITIAL_CAPACITY;//16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//12
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//1.8
threshold = newThr;//12
@SuppressWarnings({
"rawtypes","unchecked"})
//1.9
//创建一个16大小Node数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//1.10
//新数组赋值给hashmap属性table
table = newTab;
//1.11 为空不进入
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else {
// preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.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;
}
}
}
}
}
//1.12
//返回
return newTab;
}
至此,第一次put完成
第三步:第二次put
调用putVal(842049, "李四", 15, 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;
//1.0 计算得出索引位置为1
if ((p = tab[i = (n - 1) & hash]) == null)
//创建这个位置的第一个节点
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;
}
}
if (e != null) {
// existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//1.1 修改次数再次加1
++modCount;//2
//1.2
//1>12,false,执行完size变为2,就是键值对个数
if (++size > threshold)
resize();
//1.3 与hashmap无关
afterNodeInsertion(evict);
//1.4 返回
return null;
}
执行完,HashMap结构如下
图1.1
第四步:第三次put
依旧是进入putVal(1179410, "重地", 1, 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;
//1.0 目前table不为空,跳过
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//1.1 计算出i=2,与"张三"位置相同,值必然不为空,跳过,此时p为"张三"节点
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
/*1.3
判断这个桶的第一个元素是否和新节点是否相同,是赋值给e
p.hash == hash:"张三"的hash值与"重地"的hash值比较,false
(k = p.key) == key:(k="张三")=="重地",地址值不同,false
(key != null && key.equals(k)):"重地" != null && "重地".equals("张三")),false
不符合条件跳过
*/
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//1.4 不是树节点,跳过
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//1.5 遍历链表,如果到尾巴则插入,符合树化条件则树化;相等则覆盖;
for (int binCount = 0; ; ++binCount) {
//1.6 "张三"下个节点为null,(e=null)==null,true
if ((e = p.next) == null) {
/*1.7
创建一个新的结点插入到尾部,此时p.next指向的是一块新的堆内存地址,e还是null
插完之后map结构如图1.2所示
注意第四个参数next是null,因为当前元素插入到链表末尾了,那么下一个结点肯定是null。
*/
p.next = newNode(hash, key, value, null);
//1.8 0>7,false
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
//1.9 因为到达尾部,跳出循环
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//2.0 跳过
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//2.1 执行完为3
++modCount;
//2.2 跳过,size执行完为3
if (++size > threshold)
resize();
afterNodeInsertion(evict);
//2.3
return null;
}
执行完,HashMap结构如下
图1.2
第五步:第四次put
计算出的hash值和’重地’一样,接着进入putVal(1179410, "通话", 1, 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;
//1.0 table不为空,n=16,跳过
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//1.1 算出i=2,p='张三'节点,不为空,跳过
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//1.2 p.hash('张三'),hash('通话') 不一样,key也不一样,跳过
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//1.3 不是树结构,跳过
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//遍历链表
for (int binCount = 0; ; ++binCount) {
//1.4 e这时候被下个节点赋值(第一次进来,就是第一个节点(张三)的下个节点(重地)),e=p.next="重地",跳过
//1.7 e="重地"下个节点=null,符合条件,进入
if ((e = p.next) == null) {
//1.8 创建 "重地"下个节点='通话'节点
p.next = newNode(hash, key, value, null);
//1.9 1>=7,fasle
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
//2.0 退出循环
break;
}
/*1.5
"重地" 和 '通话' hash 一样,true
k("重地")和key('通话')不同,false
(key != null && key.equals(k)),false
不符合条件,跳过
*/
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//1.6 p被赋值为重地节点,相当于移动遍历的指针
p = e;
}
}
//2.1 还是为空,跳过
if (e != null) {
// existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//2.2 执行完为4
++modCount;
//2.3 3>12,执行完为size=4
if (++size > threshold)
resize();
//2.4 跳过
afterNodeInsertion(evict);
//2.5 返回
return null;
}
执行完,HashMap结构如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z36LPQk4-1644454618330)(C:\Users\Administrator\Desktop\图1.3.png)]
图1.3
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//2.2 执行完为4
++modCount;
//2.3 3>12,执行完为size=4
if (++size > threshold)
resize();
//2.4 跳过
afterNodeInsertion(evict);
//2.5 返回
return null;
}
执行完,HashMap结构如下
![在这里插入图片描述](https://img-blog.csdnimg.cn/71b68003ab1b4bef8559b87d4b6c52ad.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBARmlyZV9Ta3lfSG8=,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)
图1.3