聊聊1.8版ConcurrentHashMap优化

32 篇文章 0 订阅

hash冲突链表结构优化

hash冲突的链表长度超过阈值TREEIFY_THRESHOLD=8则转为红黑树结构

为什么阈值为8?

  1. 红黑树平均查找时间复杂度log(N),log(8)=3,链表平均查找长度为N/2,8/2=4,转换后性能更高
  2. 理想情况下,hash桶中节点的频率遵循泊松分布,桶长度超过8的概率非常小,约为6*10的负8次方,通常情况下不回发生结构转换

为什么不直接使用红黑树?

  1. 因为二叉树(红黑树其实是一种二叉树)虽然查询效率高,但是空间开销非常大,单个 TreeNode 需要占用的空间大约是普通 Node 的两倍,而在数据量很小时,红黑树与链表的查询效率不会差太多
  2. 前面阈值为8的原因中说到了,桶长度超过8的概率非常小,所以大概率情况下链表是更加的选择

resize优化

并发运输transfer

resize数据搬运运输过程并发执行,简而言之是搬运过程中数据被划分为N个区间,每个人负责一个区间,干完了就去下一个待处理区间继续干活。细节可以查看另一篇文章:https://blog.csdn.net/u010597819/article/details/96851257

  1. 搬运i节点
  2. 搬运完成后将该节点设置为ForwardingNode转发节点,之后i节点的请求都将转发至ForwardingNode节点,直至搬运完成(扩容完成)

rehash优化

将原有的rehash优化为掉,核心代码见下方,红黑树处代码逻辑相同

for (Node<K,V> p = f; p != lastRun; p = p.next) {
    // ph节点hash值
    int ph = p.hash; K pk = p.key; V pv = p.val;
    // n老hash表长度table.length
    if ((ph & n) == 0)
        ln = new Node<K,V>(ph, pk, pv, ln);
    else
        hn = new Node<K,V>(ph, pk, pv, hn);
}
// i老hash表中的下标索引位置
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);

如果老hash值与老hash表长度按位与运算为0则放在原位置,否则放在原位置+老hash表长度的位置处,为什么这么写呢?扩容后原hash值的数据一定会落在i+n位置上吗?答案是肯定的,不然岂不是凉凉了

按照长度为8的表扩容至16代入运算看下

长度8二进制表示 :01000
长度16二进制表示:10000

条件判断:

  1. hash值与8按位与运算为0表示:hash值的二进制自右向左第4位一定为0
  2. hash值与8按位与运算不为0则表示:hash值的二进制自右向左第4位一定为1

取余数

  1. hash值对8取余数则是取hash值的二进制自右向左的3位二进制对应的十进制值
  2. hash值对16取余数则是取hash值的二进制自右向左的4位二进制对应的十进制值

因此满足条件2的情况下,hash值的后四位值也就确定了

  1. hash值后三位值为原hash表的索引值i,原因不赘述,详情可以查看:https://blog.csdn.net/u010597819/article/details/104881525
  2. hash值自右向左第4位一定为1,第四位为1,对应的十进制值即老hash表长度n=8
  3. 因此满足条件2的hash值对新表长度取余数则为 i+n

总结

  1. 为什么Map的大小必须是2的幂?我们的答案又多了一点
  2. 对于hash冲突的链表或红黑树的迁移不再需要rehash
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值