1. Hashtable
- 键和值不能为空
- 初始容量为11,每次扩容的容量是上一次容量的二倍再加一
- 扩容后原先的元素不需要额外的二次hash
- 锁住整张表
2. ConcurrentHashMap
2.1 Java1.7版本中
- 由segment数组,hashEntry数组和链表组成。
- segment数组大小也代表了并发数量。而且segment的数组大小初始化后就不能更改。
- 一开始就初始化segment数组和segment[0]里面的HashEntry数组。其他的HashEntry数组懒惰初始化
- segment[i](i>0)里面的HashEntry数组是懒惰初始化,当使用到的时候才会初始化。
- segment[i](i>0)里面的HashEntry数组初始化时按照segment[0]里面的HashEntry数组的规模创建。
- 扩容是按照segment里面的HashEntry数组为基本单位进行扩容。而不是所有的segment里面的HashEntry数组都要扩容。
- 假设ConcurrentHashMap中有16个segment,每个segment里的hashEntry数组大小也恰好都是32。
- 对于一个key,它的hash值的最高4位用来定位segment,hash值得最低5位用来定位segment中的HashEntry数组下标。
2.2 Java1.8版本中
- 由Node数组和链表或红黑树实现。
- Node数组是懒惰初始化
- 初始容量16。Java8中是容量满3/4就会扩容。java1.7中是容量超过3/4才会扩容
- 构造函数的参数
- initialCapacity:表示你准备存放多少键值对的个数。它会根据这个值来动态初始化Node数组大小。实际初始化大小为initialCapacity/loadFactor再向上取2的幂。
- loadFactor:初始化时负载因子,后面扩容的负载因子还是0.75。
- 扩容的时候是原先node数组节点的移动是倒序移动。移动完毕的数组节点被替换为ForwardingNode节点。表明这里已经处理完毕。
- 如果数组中链表节点个数为一,则将这个节点迁移到新的数组中去
- 如果数组中链表节点个数比较长,则扩容的时候是复制这个链表所有节点。
- 支持并发扩容,一个线程一次迁移16个数组节点,若还没迁移完就再一次迁移。