HashMap讲解

面试阿里,HashMap 这一篇就够了 - 知乎

实现与map接口,java中用于存储key-value键值对的数据。

在jdk1.7及之前版本的hashMap底层使用的是:Entry数组+链表

在jdk1.8及以后版本的hashMap底层使用的是:Node数组+链表+红黑树

重要成员变量

   DEFAULT_INITIAL_CAPACITY = 1 << 4; Hash表默认初始容量
   MAXIMUM_CAPACITY = 1 << 30; 最大Hash表容量
   DEFAULT_LOAD_FACTOR = 0.75f;默认加载因子
  TREEIFY_THRESHOLD = 8;链表转红黑树阈值
  UNTREEIFY_THRESHOLD = 6;红黑树转链表阈值
  MIN_TREEIFY_CAPACITY = 64;链表转红黑树时hash表最小容量阈值,达不
到优先扩容
========================================================================

new HashMap<>()的时候不指定容量大小 默认是16,采用<< 位运算来设置容量大小;但是如果指定了容量是15这种,它底层会做一次修改,会改成比15大的,必须要是2的指数次幂的数,会变成16。

为什么容量要设置成2的指数次幂呢?

因为hashMap定位到数组index用的是&位运算:hashcode & (length-1),这个&运算结果和算hashcode%length的取模运结果是一样的,但是&位运算效率高出10倍,因为&位运算是最接近机器语言运算的,所以效率非常高。这样也能保证计算出来的数组index能够均匀散列分布,减少哈希碰撞。

为什么要用hashcode & (length-1)计算,为什么是length-1?

因为结果和hashcode%length相同,还有如果是hashcode & length 假设长度是16,那么&出来的结果是0或16,这种就很容易导致散列不均匀,hash冲突。

========================================================================

hashMap通过hashcode & (length-1)计算数组index,能够快速定位,所以时间复杂度是O(1),到那时获取值得时候,需要到链表去逐一去获取,所以获取值得时间复杂度是O(n)。

计算数组索引index的源码:

 hashMap的扩容加载因子:

有一个加载因子loadfactor,DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子

为什么载因子loadfactor是0.75呢?
这个不能太低,如果太低,会导致空间浪费啊,如果太高,那么会影响写入效率,扩容需要复制数据到另一个数组。这是基于空间与时间的折中考虑 牛顿二项式
当存放的数据超过75%的就会触发扩容,16*0.75=12的时候会触发。需要把

hashMap的put过程:

1、获取key的hashcode值,

2、根据hashcode & (length-1)计算出数组中所在的index下标,

3、判断index位置是否有元素,如果没有则直接存放,

4、如果index位置存在元素,则调用equals方法判断对象是否相同,若对象相同则替换原来的value,如果不同,则发生了hash碰撞,jdk1.7采用头部插入法,jdk1.8尾部插入,把新元素next指向链表中的元素,新元素存放数组中.

 hashMap的扩容:

在jdk1.7的时候,如果容量达到加载因子的大小的时候,会扩容,扩容大小是之前的2倍,满足2的指数次幂;然后调用transfer方法进行扩容,遍历原来的数组,循环遍历链表,根据hashcode重新计算新数组所在的index,然后采用头插法把链表数据写入新数组中。但是整个链表的顺序倒序了。

但是jdk1.7的扩容有个问题,单线程问题不大,但是多线程同时扩容操作的时候容易导致链表成环,导致put会出现死锁问题。

 在jdk1.8开始扩容代码都改了,调用resize方法扩容,定义了高低位头尾指针,loHead,loTail,HiHead,hiTail,for遍历旧数组根据index下标获取元素,判断元素的hashcode & 原数组大小位运算,如果元素是0则用低位指针,如果hashcode & 原数组大小结果是16, 用高位指针;那么就形成了一条高位链表一条低位链表,如果是低位链表,会把低位链表存放在index相同的新数组中,如果是高位链表,会存在index+旧数组长度的位置上。

这样就避免了1.7版本的链表环死锁问题。

 ====================================================================

在jdk1.8的hashMap还引入了红黑树,红黑树查询时间复杂度O(logn),链表转红黑树的条件:

1、数组容量必须>=64  (MIN_TREEIFY_CAPACITY )的时候链表会转红黑树,否则有限扩容;

2、只有链表过长,阈值设置TREEIFY_THRESHOLD = 8,当链表长度大于8,会转红黑树。

为什么阈值设置TREEIFY_THRESHOLD = 8?

根据加载因子 0.75  和  泊松分布计算(exp(-0.5) * pow(0.5, k),链表的大小达到8的概率很小。

==============================================================

HashMap不是线程安全的,在多线程环境下会存在线程安全问题

jdk1.7,多线程并发扩容会存在链表环,导致死锁问题。

jdk1.8,多线程并发执行put操作的时候会发生数据覆盖的情况。(两个线程同时put操作,如果hashcode相同就会出现hash碰撞,然后写入链表的时候,可能会导致第二个线程覆盖第一个线程的数据)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值