hashmap源码逻辑解析

hashmap是一个存储键值对的集合,维护的是个数组,每个键值对是Entry类型(Map键值对 对象,是个对象)分散存储在数组中,对应的key是该数据通过哈希算法所计算出来的hashcode

jdk版本结构添加元素的顺序
1.7数组+链表头插法
1.8数组+链表+红黑树尾插法

(若哈希算法的优化比较好,每次取值的空间复杂度是O(1),最差情况就是所有的数值都堆积在一个节点里面——数组退化成链表,空间复杂度O(n),JDK1.8考虑到这点,就在数组结构添加了一个红黑树)

相关参数

transient Node<K,V>[] table; //node数组,用于存储hashmap上的数据
transient Set<Map.Entry<K,V>> entrySet;//node的上层接口,主要用于存放所有的entry,便于遍历数组
transient int size;//数组中被使用的元素的数量 初始值是16
transient int modCount;//计数器,计数被修改的次数
int threshold;//扩容临界值
final float loadFactor;//负载因子 一般默认是0.75

添加值的逻辑:
这里是代码整理出来的逻辑,详情可以参考代码进行解析

链表转树触发条件
数组空间大于64
节点上的链表元素超过8个时
扩容机制
JDK1.7:
空参构造函数:以默认容量、默认负载因子、默认阈值初始化数组。内部数组是空数组。
有参构造函数:根据参数确定容量、负载因子、阈值等。
第一次put时会初始化数组,其容量变为不小于指定容量的2的幂数。然后根据负载因子确定阈值。
JDK1.8:
创建时,table是null,当首次putval时,才会指定大小(默认16)进行扩容
当前存放新值时(并非替换),已有元素个数大于阈值,就触发扩容机制

附加说明
Entry定义:将键值对结构的map转化为对象,从而能对此对象进行get和set方法,便于遍历
为什么容量一定要是2的幂数: 因为取模过于沉重 恰好当长度为2的幂,长度减一的值与哈希值进行位与运算的结果 等于 对长度进行取模,
哈希值右移十六位在进行异或操作的目的:保持高低位分布均匀,进一步分散存储
transient 修饰符的作用:对比serilizable, 保护参数不进行序列化
为什么hashmap是线程不安全的:
哈希表中某一索引位的链为 3-2-1-null,此时两个线程同时对它进行rehash(先遍历一个索引位和他的链表,再遍历每一个索引),他们都记住了 e=3,e.next = 2。
在步骤一中:线程一执行,线程二堵塞。假设线程一rehash之后新的链表为1-2-3-null,此时他刷新了2.next =3(这个之前没有),这一点很关键,之前两个线程记住的都是3.next = 2,但是2后面是谁不知道。 线程一可以顺利完成没毛病。
步骤二:线程二rehash时先把e = 3加入hashmap,然后是2(之前的e.next)2入链后本来应该轮到1了的,但是线程一把2.next改成了3,线程二的下一位又指向了3,配合他阻塞前记住的是3.next = 2,这两个互指成环了。所以这里面最关键的一点是线程一rehash时给本原来没有的2刷新了2.next = 3,如果把这一点理解了再用纸画一画就明白了。如果rehash后hashcode落在多个索引位也是一样,他们记住的前一个节点和后一个节点肯定会出现环的问题。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值