HashMap1.8以前(Entry数组+链表)
loadfactor负载因子0.75
DEFAULT_INITIAL_CAPACITY 默认容量16
threshold容量*loadfactor
put
先扩容再插入
如果定位到的数组位置没有元素 就直接插入。
如果定位到的数组位置有元素,遍历以这个元素为头结点的链表,依次和插入的 key 比较,如果 key 相同就直接覆盖,不同就采用头插法插入元素。
HashMap1.8以后(Node数组+链表/红黑树)
链表长度>8并且数组>64转化成红黑树
1.8插入链表使用尾插法
先插入再扩容(因为使用的是尾插法,如果先扩容再插入的话,某一个数组的链表重新散列之后,新的值找到新的table位置还得遍历一边才能尾插;而先插入的话,并不一定要遍历一整个链表就能插入进去,然后再通过重新散列分配他们的位置。)
resize()
是否超过最大值?threshold=Integer.MAX_VALUE无法扩充了:扩容为2倍
把原来Entry[]转移到新的HashMap
ConcurrentHashMap1.6(segment+linkedlist)
put操作
-
计算要 put 的 key 的位置,获取指定位置的 Segment。
-
如果指定位置的 Segment 为空,则初始化这个 Segment.
-
初始化 Segment 流程:
-
Segment.put 插入 key,value 值。
由于 Segment 继承了 ReentrantLock,所以 Segment 内部可以很方便的获取锁,put 流程就用到了这个功能。
-
tryLock() 获取锁,获取不到使用
scanAndLockForPut
方法继续获取。 -
计算 put 的数据要放入的 index 位置,然后获取这个位置上的 HashEntry 。
-
遍历 put 新元素,为什么要遍历?因为这里获取的 HashEntry 可能是一个空元素,也可能是链表已存在,所以要区别对待。
如果这个位置上的 HashEntry 不存在:
如果这个位置上的 HashEntry 存在:
-
如果要插入的位置之前已经存在,替换后返回旧值,否则返回 null.
这里面的第一步中的 scanAndLockForPut 操作这里没有介绍,这个方法做的操作就是不断的自旋 tryLock()
获取锁。当自旋次数大于指定次数时,使用 lock()
阻塞获取锁。在自旋时顺表获取下 hash 位置的 HashEntry。
ConcurrentHashMap1.8(node+linkedlist+bltree)
put
-
根据 key 计算出 hashcode 。
-
判断是否需要进行初始化。
-
即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
-
如果当前位置的
hashcode == MOVED == -1
,则需要进行扩容。 -
如果都不满足,则利用 synchronized 锁写入数据。
-
如果数量大于
TREEIFY_THRESHOLD
则要转换为红黑树。