Hashmap的重要变量与高频面试题整理(含答案)

本文详细探讨了HashMap的重要变量,如初始容量、负载因子和扩容机制。解释了为何长度要为2的幂次方,以及为何使用异或运算。分析了JDK1.7与1.8的区别,包括链表与红黑树的转换,并讨论了为何阈值设为8。此外,还讨论了HashMap的线程不安全问题和面试相关问题,如为什么HashMap不一开始就使用红黑树。
摘要由CSDN通过智能技术生成

hashmap重要变量

源码中定义了很多常量,有几个是特别重要的。

  • DEFAULT_INITIAL_CAPACITY : Table数组的初始化长度: 1 << 4,即 2^4=16(这里可能会问为什么要是2的n次方?)
  • MAXIMUM_CAPACITY :Table数组的最大长度: 1<<30, 即 2^30=1073741824
  • DEFAULT_LOAD_FACTOR : 负载因子:默认值为0.75。 当元素的总个数>当前数组的长度 * 负载因子。数组会进行扩容, 扩容为原来的两倍 (这里可能会问为什么是两倍?这个问题与上述2的n次方相关联)
  • TREEIFY_THRESHOLD : 链表树化的阈值,默认为8,表示载一个node(table)节点下的值的长度大于8时就会转变为红黑树
  • UNTREEIFY_THRESHOLD : 红黑树链化阈值,默认为6,表示载一个node(table)节点下的值的长度小于6时就会转变为链表
  • MIN_TREEIFY_CAPACITY = 64 :最小树化阈值,当Table所有元素超过该值,才会进行树化(为了防止前期阶段频繁扩容和树化过程冲突)。

构造器

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
复制代码

空参构造器中对属性loadFactor(加载因子)进行了赋值操作,初始值为16。

注意: JDK8中创建完HashMap对象后并没有立即创建长度为16的数组。

最显而易见的区别

  • JDK 1.7 : Table数组+ Entry链表;
  • JDK1.8 : Table数组+ Entry链表 ==> 红黑树;(可能会问为什么要使用红黑树?)

为什么使用链表+数组?

因为为了避免hash冲突的问题。由于我们的数组的值是限制死的,我们在对key值进行散列取到下标以后,放入到数组中时,难免出现两个key值不同,但是却放入到下标相同的格子中,此时我们就可以使用链表来对其进行链式的存放。

我⽤LinkedList代替数组结构可以吗?

可以

那既然可以使用进行替换处理,为什么又偏偏使用到数组呢?

因为用数组效率最高! 在HashMap中, 定位节点的位置是利用元素的key的哈希值对数组长度取模得到。 此时,我们已得到节点的位置。显然数组的查找效率比LinkedList大(底层是链表结构)。

ArrayList,底层也是数组,查找也快,为啥不⽤ArrayList?

因为采用基本数组结构,扩容机制可以自己定义,HashMap中数组扩容刚好是2的次幂,在做取模运算的效率高。⽽ArrayList的扩容机制是1.5倍扩容。

当两个对象的 hashCode 相同会发生什么?

因为 hashCode 相同,不一定就是相等的(equals方法比较),所以两个对象所在数组的下标相同,"碰撞"就此发生。又因为 HashMap 使用链表存储对象,这个 Node 会存储到链表中。

你知道 hash 的实现吗?为什么要这样实现?

JDK 1.8 中,是通过 hashCode() 的高 16 位异或低 16 未实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度,功效和质量来考虑的,减少系统的开销,也不会造成因为高位没有参与下标的计算,从而引起的碰撞。

为什么要用异或运算符?

保证了对象的 hashCode 的 32 位值只要有一位发生改变,整个 hash() 返回值就会改变。尽可能的减少碰撞。

为什么是2的幂次?与数字2的关系

首先为什么长度要是2的n次方?

这依然关系到hash冲突。

简单来说,就是如果是2的幂,就能减少hash冲突的出现

因为这个key存放到数组中哪个位置,,源码中有个按位与来判断,即 hash & (length - 1) ,如果是2的幂,那么这个减一的操作会让最后4个位数都是1111,进而保证哈希值能分布早0-15之间。如果不是2的幂的话,有可能出现某个哈希桶(可以理解为数组中某个位置)是一直为空的,不能完全利用好数组。

注意: hash值是个32位的int型

那么如果不是容量不是2的幂呢?

源码中有个静态方法 private static int roundUpToPowerOf2 ,这个方法会将容量扩充为2的幂。

int rounded = number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY
 : (rounded = Integer.highestOneBit(number)) != 0 ? 
 (Integer.bitCount(number) > 1) ? rounded << 1 : rounded
                                        : 1;
复制代码

流程图如下:

hashmap为什么是二倍扩容?

容量n为2的幂次方,n-1的二进制会全为1,位运算时可以充分散列,避免不必要的哈希冲突。所以扩容必须2倍就是为了维持容量始终为2的幂次方。

阶段总结

Hashmap的结构,1.7和1.8有哪些区别

  1. JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法,那么他们为什么要这样做呢?因为JDK1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。
  2. 扩容后数据存储位置的计算方式也不一样:hash & (length-1)
  3. JDK1.7的时候使用的是数组+ 单链表的数据结构。但是在JDK1.8及之后时,使用的是数组+链表+红黑树的数
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值