HashMap put源码解析
2021-11-20 09:33:24 0 举报
HashMap put源码解析
作者其他创作
大纲/内容
newThr == 0
n = (tab = resize()).length
// 将12赋值给newThr // 将16赋值给newCapnewCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))
(e = p.next) == null
2
此方法中有很重要的一点:也就是如果table的长度不足64的时候,继续进行扩容操作,不树化tab.length < MIN_TREEIFY_CAPACITY=64,进行resize操作,不进行树化桶 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize();
V oldValue = e.value获取该hash key的旧值
红黑树添加相关,代码暂时不看了注意:如果hash key相同的,返回节点node如果没有找到相同的,添加树节点后 返回null
if
如果当前索引位置是空的,直接生成新的节点在当前索引位置上
do while 循环当前节点到最后(e = next) != null
说明只有一个元素,没有链表节点,则用新的数组长度进行位置计算newTab[e.hash & (newCap - 1)] = e
如果当前tab[i] 节点已经树化
Y
第一次添加元素,才进行初始化 16
e instanceof TreeNode
hash算法
oldThr > 0
for循环遍历旧table (oldTable)
如果key为null的话,在这个时候就会把值放在0号的位置因为hash = 0
i = (n - 1) & hash这一步重要“”其实就是一个寻址的过程这里用的是 n-1也就是 16-1 32-1所以这里的i就是0000 0000 0000 0000 1101 1011 0101 00100000 0000 0000 0000 0000 0000 0000 1111 == 150000 0000 0000 0000 0000 0000 0000 0010也就是,这里真正寻址起作用的就是后四位,也就是和n-1对应的四位其实这里也对应了之前的^ 异或运算,让高低位参与运算,导致了hash的散列,然后在这里进行n-1的&运算,这样就可以有效的优化hash的碰撞其次,在扩容的时候还会 用到,也就是新增的bit位,如果是1就是 i + 16,如果是0,就还是在原来的角标下面0000 0000 0000 0000 0000 0000 0000 1111 == 150000 0000 0000 0000 0000 0000 0001 1111 == 31这个就是是所谓的新增bit位刚刚在扩容的新算法 中看到的是在新槽还是旧槽的算法是(e.hash & oldCap) == 0,也就是0000 0000 0000 0000 1101 1011 0101 00100000 0000 0000 0000 0000 0000 0001 0000 == 16所以还是一样的,就看新的bit位,是0 ,与出来是0.旧槽位,是1 新槽位,也就是 最后 + 这个值得时候,如果上面那里是0,也就是+0了,如果行的bit位是1,那就是+16了
HashMap map = new HashMap() ;
注意:remove的时候,进行退化链表的操作并不是按照节点数量来的参考:https://my.oschina.net/u/580483/blog/4294852触发时机:如果root,root的right或left,root的left的left,有一个为null,说明红黑树太稀疏代码 first.untreeify(map)if (root == null || root.right == null || (rl = root.left) == null || rl.left == null) { tab[index] = first.untreeify(map); // too small return; }
afterNodeInsertion(evict);
put
for(无限循环)int binCount = 0; ; ++binCount
int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
return oldValue
2.1
oldTab != null
N 说明有值hash冲突
resize
新槽位进行链表拼接
所以,这一步其实就是有相同的hash key,进行值得修改
e 如果是树节点
按照红黑树的方法添加节点putTreeVal
重要
添加树节点
e != null
说明tab[i] 有数值,这里就会进行链表、树化、或者值得覆盖判断
e.next == null
else
1、oldCap >= MAXIMUM_CAPACITY设置threshold = Integer.MAX_VALUE2、反之: oldThr << 1 , 旧长度 * 2
this.threshold = tableSizeFor(initialCapacity)这一步会进行长度的操作,也就是尽可能的靠近2的倍数,但是如果小于16 返回0
map.put(\"name\
如果是LinkedHashMap,这个newNode方法会被重写
判断当前索引是否 == NULL
说明该节点和当前的tab[i]节点冲突,且key不一样,需要进行链表的添加
返回
3
p instanceof TreeNode
hash(key)
再次循环
这一步这里的table还是空
重新计算newThr =
1、 key.hashCode() 获取对应的hashcode值 int2、 h >>> 16 是什么,有什么用 无符号向右边移动,也就是 因为int是32位,且正常情况下由于绝大多数情况下length一般都小于2^16即小于65536,所以移动16位0000 0100 1011 0011 1101 1111 1110 0001 >>> 16 0000 0000 0000 0000 0000 0100 1011 0011(h = key.hashCode()) ^ (h >>> 16) ^异或运算,就是按二进制位对齐,相同为假,相异为真0000 0100 1011 0011 1101 1111 1110 0001 0000 0000 0000 0000 0000 0100 1011 0011 0000 0000 0000 0000 1101 1011 0101 0010所以:这里其实就是用高16位和低16位进行
如果数组为空,使用 resize 方法初始化
算法 : (e.hash & oldCap) == 0,也就是0000 0000 0000 0000 1101 1011 0101 00100000 0000 0000 0000 0000 0000 000font color=\"#ff0000\
newCap = oldThr
putVal
LinkedHashMap
//记录 HashMap 的数据结构发生了变化++modCount;//如果 HashMap 的实际大小大于扩容的门槛,开始扩容//threshold = 下一次调整大小时的size的值。(capacity * load factor). if (++size > threshold) resize(); afterNodeInsertion(evict); return null;
2.2
!onlyIfAbsent || oldValue == null
树化
newTab
else if
N
链表遍历过程中,发现有元素和新增的元素相等,结束循环
当链表的长度大于等于 8 时,链表转红黑树
1、退化链表触发时机扩容 >= 8remove 不太理解,好像是和红黑树的稀疏相关,和6 、 8 无关
清空旧数组中的值oldTab[j] = null
(e.hash & oldCap) == 0
int oldCap = (oldTab == null) ? 0 : oldTab.length获取tab的长度
如果 key 的 hash 和值都相等
break 退出循环
这里会判断是否需要转换链表length <= 6 的时候而且,这个树的退化成链表, 只会发生在扩容阶段,也就是当你remove的时候,哪怕树节点个数< 6 不会进行转换
说明头结点是null 的元素,不变
resize()
onlyIfAbsent :如果true,则不改变已经存在的value,默认传进来false,可覆盖或者旧值得value为null,直接覆盖
寻址算法
afterNodeAccess该方法 hashmap没实现,Linkedhashmap 会有实现,我们学到再看
默认走这里
2.4
e = p.next 表示从头开始,遍历链表 p.next == null 表明 p 是链表的尾节点
(p = tab[i = (n - 1) & hash]
旧槽位进行链表拼接
1
table == null|| length = 0
1、重新设置 threshold = newThr 需要扩容的size2、创建新的容量 (旧的*2)的新的数组newTab
赋值 table = newTab
2.3
修改
关于e如果key 和 hash 都一样,则e = 当前p如果p节点是treeNode,则e = 添加树化之后的treeNode
(e = oldTab[j]) != null
(oldCap > 0
oldThr = threshold需要扩容的阈值,16*0.75 = 12
HashMap map = new HashMap(2) ;
剩下的,就说明已经是链表了,所以这里就相对比较简单了会生成两个链表,一条是在旧tab位置的数据,一条是新table位置的就比如:当前节点的链表长度 为 5 ,则就可能是2个元素在旧位置index =3 ,3个元素在 3+16 = 19的位置
查看上面的resize方法
p = e更改循环的当前元素,使 p 在遍历过程中,一直往后移动。
e = p如果 key 的 hash 和值都相等,直接把当前下标位置的 Node 值赋值给临时变量
0 条评论
回复 删除
下一页