彻底搞懂HashMap
文章目录
一、HashMap结构
数组加链表
key通过hashCode得到数组位置,因为哈希本身存在概率性,哈希值相同的值会插入到相同的数组索引下形成链表
二、扩容
1.扩容原因
因为数组长度是有限的,数组会随着值得增加而进行扩容
2.扩容条件
1.capaCity:数组长度
2.LoadFactor:负载因子,默认0.75f
举例:若当前数组长度为100,当插入第76个值时,数组进行扩容
3.扩容方法
1.创建新的的空数组,长度为原来2倍
2.ReHash:遍历原数组,重新计算Hash到数组
*采用重新计算而非复制 因为长度扩大Hash规则随之改变
三、插入
1.插入方法
1.7之前使用头插法,1.8之后使用尾插法
2.尾插原因
单链表使用头插时同一位置新元素终被放在链表位置多线程同时进行插入会形成环形链表 线程不安全
使用头插会改变链表的上的顺序,但是如果使用尾插,在扩容时会保持链表元素原本的顺序,就不会出
现链表成环的问题了
3.碰撞问题
利用“拉链法”处理HashCode的碰撞问题;当我们将键值对传递给put方法时,他调用键对象的hashCode()方法来计算hashCode,然后找到bucket(哈希桶)位置来存储对象;当用get方法获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当碰撞发生了,对象将会存储在链表的下一个节点中。hashMap在每个链表节点存储键值对对象。当两个不同的键却有相同的hashCode时,他们会存储在同一个bucket位置的链表中。键对象的equals()来找到键值对
三、多线程中使用hashMap
多线程中不推荐使用hashMap即使1.8采用了红黑树 因为haspMap中gep和put并没有加同步锁无法保证上一秒的get和下一秒的put为相同的值
多线程中使用HashTable和ConcurrentHashMap
HashTable底层源码方法上锁,保证最多允许一个线程方法
ConcurrentHashMap优化了并发推荐使用
四、为什么重写equals方法要重写hashCode
因为在java中,所有的对象都是继承于Object类。
Ojbect类中有两个⽅法equals、hashCode,这两个方法都是用来比较两个对象是否相等的。
在未重写equals我们是继承了object的equals方法,equals是比较两个对象的内存地址,
显然我们new了2个对象内存地址肯定不一样对于值对象,
==比较的是两个对象的值对于引用对象,比较的是两个对象的地址
*链表可能会存在多个值,重写equals方法会导致地址值发生改变,重写hashCodez保证相同的对象返回相同的Hash值,不同的对象返回不同的hash值
五、为什么默认指定16个长度
当数组长度是通过2的次方扩充的,那么会发现以下规律:元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置
结尾
java1.8之后采用了红黑树优化列表但这个之后另开帖子来说