• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

张秋天

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

《集合》之hashmap

 

 

HashMap底层采用了数组+链表+红黑树

 

put操作

1)判断数组是否为空,为空进行初始化

2)不为空,计算 key 的 hash 值,通过(n - 1) & hash计算哈希槽;

3)查看哈希桶是否存在数据,没有数据 构造一个 Node节点 存放在 table[index] 中;

4)存在数据,hash冲突,  判断key是否相等,

相等,用新的value替换原数据;

若不相等,判断当前节点类型是不是树型节点,是树型节点,创造树型节点插入红黑树;

  不是红黑树,创建普通Node加入链表;

 链表长度 > 8,数组长度 >= 64,转红黑树。数组长度 < 64,扩容。

 

5)插入完成之后判断当前节点数是否大于阈值,大于,扩容为原数组的二倍

 

哈希函数

hash函数是先拿到 key 的hashcode,32位的值,高16位和低16位进行异或操作。

该函数也称为扰动函数,尽可能降低hash碰撞。

 

容量为什么 2^N 

数组下标的计算方法是 (n - 1) 与 hash ,速度比取模运算快

索引值在容量中,不会超出数组长度

尽量把数据分配均匀,较少碰撞,让 HashMap 存取高效。

 

 扩容

1)数组为空,或者数组的长度为0时,扩容

1)链表长度 > 8,数组长度 < 64,会引发扩容

1)默认负载因子0.75,数组中已存储的元素个数 > 数组长度75%,扩容

 

创建一个长度为原来数组长度 两倍 的新数组。

重新对原数组中的Entry对象进行哈希运算,确定在新数组中位置。

 

元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit

不需要重新计算hash,看原来的hash值新增 bit是 1还是0,0索引没变,1 索引变成“原索引+ n”

 

负载因子0.75

提高空间利用率和 减少查询成本的折中,主要是泊松分布

加载因子过高,例如为1,减少了空间开销,提高了空间利用率,但增加了查询时间成本;

加载因子过低,例如0.5,减少查询时间成本,但是空间利用率很低,同时提高了rehash 的次数。

为什么不直接红黑树,先链表再转红黑树

树节点的大小是链表节点大小的两倍,所以有足够的节点才用

 

转为树使得查找的速度更快,但是节点数少的时候,红黑树内存上的劣势会超过查找的优势,

在节点数比较多的时候,综合考虑,红黑树比链表要好。

 

链表长度是 8,不是9、10

理想情况下,哈希表中节点长度遵循泊松分布,8的时候概率就已经很小了,往后调整没有多大意义。

 

当hashCode离散性很好的时候,树型bin用到的概率非常小,因为数据均匀分布在每个bin中,几乎不会有bin中链表长度会达到阈值。

可以看到,链表长度达到8,概率为0.00000006,几乎不可能事件

 

1.7 死循环

扩容后链表中的节点在新的hash桶使用头插法插入,新的hash桶会倒置原hash桶中的单链表。

多个线程同时扩容的情况下,产生一个存在闭环的单链表,死循环。


1.8 采用尾插法,保证了链表的顺序与之前一致。

1.8中链表过长时会转换为红黑树,在转换为红黑树前,也是先根据尾插法生成新链表再进行转换。

 

 JDK1.8死循环

红黑树成环,两个红黑树节点的父节点相互引用。

 

 

 

 

 

 

 

 

posted on 2020-12-19 22:05  张秋天  阅读(126)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3