不支持并发操作
HashMap里面是数组,数组中的每一个元素都是单向链表
capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的2倍
loadFactor:负载因子,默认为 0.75,负载因子越接近1,数组就越密,查找效率低,越小就越疏,数组的利用率就低
threshold:扩容的阈值,等于capacity*loadFactor
在计算key的哈希值的之后让key的hash值与高16位进行异或运算,减少了hash冲突
扩容:当前的 size 已经达到了阈值,并且要插入的数组位置上已经有元素,将容量扩大为原来的两倍,并将原来的数组迁移到现有数组中,迁移过程中,会将原来 table[i] 中的链表的所有节点,分拆到新的数组的 newTable[i] 和 newTable[i + oldLength] 位置上。如原来数组长度是 16,那么扩容后,原来 table[0] 处的链表中的所有元素会被分配到新数组中 newTable[0] 和 newTable[16] 这两个位置
默认容量:16
大小为大于输入数字的最小2的n次方,不输的话默认是16
阈值等于capacity*loadFactor
取key的hash值的低n位(n根据数组大小确定),假如数组大小为32.那么取hash的低5位
将新值放入到链表的表头(注意是表头)
假如当前的容量大于阈值并且要存放的数组位置有值了,那么就要扩容,扩容后重新计算存放的位置
采用数组+链表+红黑树
链表中元素有8个并且散列表容量大于64时会自动转化为红黑树
根据第一个元素是Node或者是TreeNode来确定是链表还是红黑树
扩容的时候假如是链表,会被拆分两条链表
并发安全
concurrencyLevel:并发数,Segement默认是16,表示最多可以同时支持 16 个线程并发写
initialCapacity:整个ConcurrentHashMap的容量,Segment 数组不可以扩容
loadFactor:为每个Segement内部使用
segementshift:2的sshift次方等于ssize,segmentShift=32-sshift;segementMask=ssize-1(确保低位都是1来使得获得的hashcode均匀分布
不允许key和value是null
ConcurrentHashMap是一个Segement数组,可以对单个Segement加锁
Segment 数组不可以扩容,默认每个Segement容量大小为2
初始化完成后得到一个Segement数组,只初始化了一个Segement[0]
Segement内部是数组HashEntry+链表
每一个segement对象都有一个count数(volicate)来统计内部的entry数量,加入count大于阈值会扩容成原来的两倍;统计size的时候前两次不加锁(如果出来的数据变了的话),在将remove和put方法锁住进行统计
get方法不用加锁,所用的变量都是volilate修饰,volatile可以保证内存可见性,所以不会读取到过期数据。
remove()元素后该元素后面的元素顺序不变,前面的会变成倒序(是从表头插入的)
加入了红黑树
构造函数初始化(设置sizeCtl)
通过提供的初始化容量大小设置sizeCtl
sizeCtl:(1.5 * initialCapacity + 1),然后向上取最近的2的n次方
sizeCtl数值:-1(初始化) ,-n(有n-1个线程正在扩容),正数(下一次要扩容的大小)或0(没有初始化)
通过CAS将sizeCtl设置成-1,通过判断sizeCtl来判断是否被初始化,并设置默认值
对链表加锁,遍历链表,建立红黑树,并设置到对应的位置.不一定转化成红黑树,有可能就是数组扩容
将旧的数组大小分配给多个线程
数组第一个元素的hash值假如是MOVED(-1),表明正在迁移,迁移要判断是链表还是红黑树
迁移可以多线程并发,线程加入使sizectl自增,线程退出后sizectl自减
数据迁移的时候要得到当前数组位置元素的Sychronized锁