1、HashMap
1 计算hash桶位置公式的好处
// 1 计算hash桶位置的公式
(n - 1) & hash // 这个公式的作用是为了计算出hash值所处的桶位置,其实就是 hash % n.
// 好处是位运算效率更高
// 但是有个条件就是 n必须是2^n,因为只有这n = 2^n时,(n - 1) $ hash == hash % n
// 2 hash 的计算方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
// 将hash值的低16位,异或 hash值的高16位,得到一个新的hash值
}
为了让hash值的每一位都能影响结果,能够起到减少hash冲突的作用
2 put方法逻辑
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal( int hash, K key, V value, boolean onlyIfAbsent, boolean evict){
// 1 判断 table == null || table.length == 0
// true: 调用resize()
//2 判断目标hash位置是否为null
// true: 当前位置还没有元素,新建Node节点直接插入目标位置
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//false: 当前位置已有元素
// 3 将元素插入到桶中
// 3.1 if 如果 插入的元素 等于桶的第一个元素 则什么都不干
// 3.2 else if 如果桶第一个元素类型是 TreeNode 则调用putTreeVal将元素插入到红黑树
// 3.3 else 将元素插入到链表尾部, 插入后需要判断是否链表的长度是否 >= 树化阈值, 大于则将链表树化。 循环过程中如果判断key是否存在,若存在更新当前节点为最新。
// 4 根据 onlyIfAbsent 判断是否更新 value值
}
- 判断table是否为空,如果为空则初始化
根据当前key的hashcode定位到具体桶,并判断是否为空,为空则表示无hash冲突则直接创建node并插入桶中
将key hash之后取得所定位得桶
- 如果桶为空则直接返回null
- 否则判断桶得第一个位置得key是否为查询得key,是就直接返回value。
- 如果第一个不匹配,则判断它得下一个是红黑树还是链表。
- 红黑树就按照树得查找方式返回值
- 不然就按照链表得方式遍历匹配返回值
从两个核心方法(get/put)可以看出1.8对大链表做了优化,修改为红黑树之后查询直接提高到了o(logn)
4 HashMap 1.7和1.8有何变化
1、ConcurrentHashMap
1.7版本
segment 数组 + 链表 组成
concurrentHashMap采用分段锁技术,其中Segment继承于ReentrantLock。 当一个线程占用锁访问一个segment的时候,不影响其他segment。
**
- 首先会尝试获取锁,如果获取失败则自旋获取锁,若自旋重试次数达到阈值,则改为阻塞锁获取。
get逻辑比较简单,只需要将key通过hash之后定位到具体的segment,再通过一次hash定位到具体的元素上。 HashEntry 中的value是用valatile关键字修饰的,保证了内存可见性。每次获取都是最新值。
1.8版本优化
抛弃了原有的segment分段锁,而是采用了 CAS + synchroinzed 保证并发安全性
跟HashMap很像,也把之前的HashEntry改成了Node,但是作用不变,把值和next采用volatile去修饰,保证了可见性,并且引入了红黑树。
值的存取过程?以及如何保证线程安全?
concurrentHashMap 在进行put操作
- 根据key计算出 hashcode
- 判断是否需要进行初始化
- f 即为当前key定位出的Node,如果为空表示当前位置可以写入数据,利用CAS尝试写入,失败则自旋保证成功
- 如果当前位置的hashcode = MOVED = -1 ,则需要扩容。
- 如果都不满足,则利用synchroinzed锁写入数据。
- 如果数量大于 TREEIFT_THRESHOLD 则需要转为红黑树
concurrentHashMap的get操作
- 根据计算出来的hashcode寻址,如果在桶上那么就直接返回。
- 如果是红黑树那就按照树的方式获取值
- 不满足就按照链表的方式遍历获取值
1.8在1.7的数据接口上做了大的改动,采用红黑树之后可以保证查询效率o(logn),甚至取消了ReentrantLock改为synchronized,