HashMap及ConcurrentHashMap

一. Java 1.7
1.HashMap
HashMap 默认初始容量是 16

长度始终保持 2 的 n 次方

扩容:每次扩容为原来的 2 倍

1)初始化:

当初始化 HashMap 的大小时,HashMap 并不会使用我们传进来的 initialCapacity 参数直接作为初识容量。HashMap 构造函数会帮我们计算一个相对合理的值当做初始容量。所谓合理值,其实是找到第一个比用户传入的值大的 2 的幂。比如传 7 的话,hashmap 会帮我们初始化一个容量为 8 的 hashmap,传 9 的话,hashmap 会通过计算,帮我们创建一个容量为 16 的 hashmap。

2)加载因子为 0.75:

实际存储容量 > initialCapacity *0.75时 会进行扩容,每次扩容都需要重建 hash 表,是非常影响性能的。同样设置过大浪费内存,因此设置一个合适的初始容量是有必要的。

可通过expectedSize /0.75F + 1.0F 计算默认初始的大小,expectedSize 为要存的数量

3)底层实现:

底层使用一个 entry 对象数组 + 链表构成
底层通过将 <key,value> 封装成一个对象 entry,然后通过计算 key 的 hashcode 值,再将hashcode 值 与 数组长度-1进行与运算后定位到数组 index,若该 index 无元素,则直接插入,若该 index 有元素,则遍历该位置链表,有该 key 则修改其值,没有则插入(Java 7 使用头插法,Java 8 使用尾插法)

2.HashTable
HashTable 实现线程安全的原理:在可能产生并发安全问题的地方加上 synchronized 关键字,当某个线程操作hash表时给整个散列表加锁,保证并发线程安全性,但是显然这是不合理的,因为某个线程操作散列表的内容是有限的,但是锁了整个表,虽然保证了线程安全性,但是严重了影响性能

3.ConcurrentHashMap
底层原理:HashEntry +链表 + Segment + ReentrantLock

将整个 hashMap 散列表分段,每一段称之为一个 Segment ,默认大小是16,在并发情况下,通过 ReentrantLock 分段加锁保证并发线程安全,在 put <key,value> 时,将<key,value>封装成一个 HashEntry ,通过 key 的 hashcode 值先计算其位于哪一个Segment,若多个线程访问同一个 segment 时,只能有一个线程获得该 Segment 的锁,其余访问该 segment 的线程只能阻塞等待,但是访问不同 segment 的线程不受影响,可以并发执行

Segment 数组大小称之为并发度,代表哈希表分的段数,一经初始化确定就不再变化

相较于 hashTable 通过分段锁,在保证了 HashMap 的线程安全性的同时,大大提高了其性能

二. Java 1.8
1.HashMap
底层使用 数组 + 链表 + 红黑树 构成

底层通过将 <key,value> 封装成一个对象 Node,然后通过计算 key 的 hashcode 值,再将hashcode 值 与 数组长度-1 进行与运算后定位到数组 index,若该 index 无元素,则直接插入,若该 index 有元素,则遍历该位置链表,有该 key 则修改其值,没有则插入(Java 7 使用头插法,Java 8 使用尾插法)

不同点:当链表元素个数大于等于 8 时,会判断当前数组长度是否大于64,小于64则进行扩容,大于64,则将该节点的链表转为红黑树

2.ConcurrentHashMap
JDK1.8 的 ConcurrentHashMap 数据结构使用的是和 HashMap 一样的数据结构:数组 + 链表 + 红黑树。ConcurrentHashMap 中包含一个 table 数组,其类型是一个 Node 数组;而 Node 是一个继承自 Map.Entry<K, V> 的链表,而当这个链表结构中的数据大于等于 8 并且数据长度大于64,则将链表升级为 TreeBin 类型的红黑树结构

并发安全性:整个Node数组使用 volatile 关键字修饰 ,保证了多线程下哈希表的可见性,CAS + synchronized 关键字方式时加锁的对象是每个链条的头结点,也就是锁定的是冲突的链表,所以再次提高了并发度

当多线程访问同一个元素节点时:

若该元素节点为空:则触发乐观锁机制,线程获得该 node 节点对象的版本号,在添加元素之前判断版本号是否与旧的保持一致,若一致则添加,若不一致则执行自旋操作

若该元素节点不为空:则使用 synchronized 关键字进行加锁,锁住当前链表的头节点,只能有一个线程获得锁,其余线程必须阻塞等待,然后获得锁的线程进行操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值