详情见:HashMap及源码
hashMap的底层结构
1.8之前(数组加链表)
1.8及之后 (数组、链表、红黑树),若链表长度大于8且数组长度大于64,转换为红黑树(小于64优先扩容),减少hash冲突搜索时间
初始化大小 16
扩容(超过负载因子阈值 0.75 ),每次扩容多少
源码:entry数组,entry/Node节点,节点next指针
HashMap的put流程?大体上三步
- 根据键的hash码(不是hashCode)计算数组中的位置。
- 检查数组该位置是否为空,为空则创建一个Entry存放键值对。
- 如果该位置有其他键值对,判断hash码和equals方法是否相同,相同则将新的值替换旧值。
- 如果不相同,则遍历链表或者红黑树查找是否有相同的hash码和equals方法。 有则替换旧值。如果都不相同则创建新的Entry/Node添加到链表头部。
- 检查链表长度是否超过阈值(8),且数组长度大于64,如果是转换为红黑树。
- 检查负载因子是否超过阈值(0.75),是则进行扩容。
总结:先计算找位置,再判断(添加或者替换),最后进行两次检查。
HashMap如何扩容的?
-
新建一个大小为2n的数组,把原数组中的数据 计算hash后 拷贝过去。
hashMap的hash方法
根据hashCode值调用hash方法 再次哈希,得到hash值与数组长度取模,才是key真正需要放置的位置。
为什么需要再次hash?
防止一些性能比较差的hashCode方法,减少哈希碰撞。
HashMap为什么用红黑树不用其他树?(美团)
二叉排序树:可能出现极度不平衡的情况。
平衡二叉树:AVL追求严格平衡(红黑树不追求),AVL树增加删除节点时旋转次数比 红黑树多,因此红黑树更容易维护。 (红黑树更适合增删频繁的场景)(在这种场景下红黑树的性能比AVL树好)
为什么HashMap线程不安全?hashMap线程不安全的场景
HashMap的死循环及数据覆盖问题_hashmap数据覆盖问题-CSDN博客
在jdk1.7中,线程不安全 主要是并发扩容的时候,数组扩容 头插法插入entry的时候可能 造成链表死循环。
HashMap的扩容操作,重新定位每个桶的下标,并采用头插法将元素迁移到新数组中。头插法会将链表的顺序翻转,这也是形成死循环的关键点。
在jdk1.8中, ****采用尾插法解决了扩容时链表死循环的问题。但多线程put的时候仍然有数据覆盖的问题。
为什么1.7中多线程头插法会死循环,说步骤?(难,太难了,偶尔问)
说说 t1执行完扩容之后指针发生了改变,t2被唤醒此时链表已经倒置,然后t2执行操作导致死循环。
hashCode和equals方法重写规则
- 以 对象内容 决定hashCode和equals,如果对象内容相同equals方法要为true。
- 如果 o1.equals(o2) 为true,那么o1.hashCode必须等于o2.hashCode
- 因为o1.equals(o2)代表两个对象逻辑相同,而hashCode相同是为了保证去同一个hash槽里找
- 如果o1.hashCode==o2.hashCode,不意味着o1.equals(o2) 为true
-
hashCode相同可能是巧合,并不代表两个对象逻辑相同。
-
为什么链表长度是8就转红黑树?
为什么HashMap桶长度超过8才会转换成红黑树_hashmap、concurrenthashmap初始化阈值为什么要是8,才转为红黑树?-CSDN博客
个人觉得:
- 首先树节点占用空间约是 链表节点两倍,太短没必要转换成树。
- 作者根据统计,链表长度为8的概率极低(千万分之一),个人感觉为了防止恶意使用带来的性能下降。
HashMap中同一链表中对象key的hashCode值相同吗?
当然不同(大部分情况不同),例如当数组长度为16时,hash值(由hashCode再次hash得出)为1和17的对象都存放在数组同一个位置的链表中,很少会出现不同key对象的hashCode值相同的情况(有这种可能,这也是为什么 需要重写equals方法)