HashMap
- 底层数组+链表实现,
- 可以存储null键和null值,
- 线程不安全
- 默认大小: 16, 增长因子:0.75;扩容点规则(什么时候扩容): 16*0.75=12;扩容方法:扩容为原来的2倍
- 扩容:newsize = oldsize*2,size一定为2的n次幂
- 扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入
- 插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)
- 当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀
- 计算index方法:index = hash & (tab.length – 1)
HashTable
- 底层数组+链表实现,
- key还是value都不能为null,
- 线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化
-
默认大小: 11, 增长因子:0.75;扩容点规则(什么时候扩容): 11*0.75=12,扩容方法:扩容为原来的2倍+1
-
扩容:newsize = olesize*2+1
- 计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length
ConcurrentHashMap
- (1.8)底层结构是散列表(数组+链表)+红黑树,这一点和HashMap是一样的。
- ConcurrentHashMap的key和Value都不能为null
- Hashtable是将所有的方法进行同步,效率低下。而ConcurrentHashMap作为一个高并发的容器,它是通过部分锁定+CAS算法来进行实现线程安全的。CAS算法也可以认为是乐观锁的一种~
- 在高并发环境下,统计数据(计算size...等等)其实是无意义的,因为在下一时刻size值就变化了。
- get方法是非阻塞,无锁的。重写Node类,通过volatile修饰next来实现每次获取都是最新设置的值
JDK1.7中ConcurrentHashMap采用了数组+Segment+分段锁的方式实现。ConcurrentHashMap中使用Segment
代替了HashMap中的链表,而Segment继承了ReentrantLock
。每次加锁的操作, 实际是锁住了每一个Segment
,这样保证了整体的线程安全。
坏处:这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长
好处:写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment。
JDK8中ConcurrentHashMap参考了JDK8 HashMap的实现,采用了数组+链表+红黑树的实现方式来设计,内部大量采用CAS操作,JDK8中彻底放弃了Segment转而采用的是Node。
从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树。
1.数据结构:取消了Segment分段锁的数据结构,取而代之的是数组+链表+红黑树的结构。
2.保证线程安全机制:JDK1.7采用segment的分段锁机制实现线程安全,其中segment继承自ReentrantLock。JDK1.8采用CAS+Synchronized保证线程安全。
3.锁的粒度:原来是对需要进行数据操作的Segment加锁,现调整为对每个数组元素加锁(Node)。
4.链表转化为红黑树:定位结点的hash算法简化会带来弊端,Hash冲突加剧,因此在链表节点数量大于8时,会将链表转化为红黑树进行存储。
5.查询时间复杂度:从原来的遍历链表O(n),变成遍历红黑树O(logN)。