HashMap面试,看这一篇就够了

本文深入探讨HashMap面试中的关键点,包括其数据结构、寻址、存储、扩容和红黑树等核心概念,帮助读者理解HashMap的细节和优化策略。
摘要由CSDN通过智能技术生成

历史热门文章:

  1. 七种方式教你在SpringBoot初始化时搞点事情
  2. Java序列化的这三个坑千万要小心
  3. 可以和面试官聊半个小时的volatile原理
  4. Java中七个潜在的内存泄露风险,你知道几个?
  5. JDK 16新特性一览
  6. 啥?用了并行流还更慢了
  7. InnoDB自增原理都搞不清楚,还怎么CRUD?

前言

在一场面试中最能打动面试官的其实是细节,候选人对细节的了解程度决定了留给面试官的印象到底是“基础扎实”还是“基础薄弱”,如果候选人能够举一反三主动阐述自己对一些技术细节的理解和总结,那无疑是面试过程中的一大亮点。HashMap是一个看着简单,但其实里面有很多技术细节的数据结构,在一场高端的面试中即使不问任何红黑树(Java 8HashMap引入了红黑树来处理极端情况下的哈希碰撞)相关的问题,也会有很多的技术细节值得挖掘。

把书读薄

Java 7HashMap实现有1000多行,到了Java 8中增长为2000多行,虽然代码行数不多,但代码中有比较多的位运算,以及其他的一些细枝末节,导致这部分代码看起来很复杂,理解起来比较困难。但是如果我们跳出来看,HashMap这个数据结构是非常基础的,我们大脑中首先要有这样一幅图:

img

图片来源:https://www.cnblogs.com/tianzhihensu/p/11972780.html

这张图囊括了HashMap中最基础的几个点:

  1. JavaHashMap的实现的基础数据结构是数组,每一对key->value的键值对组成Entity类以双向链表的形式存放到这个数组中
  2. 元素在数组中的位置由key.hashCode()的值决定,如果两个key的哈希值相等,即发生了哈希碰撞,则这两个key对应的Entity将以链表的形式存放在数组中
  3. 调用HashMap.get()的时候会首先计算key的值,继而在数组中找到key对应的位置,然后遍历该位置上的链表找相应的值。

当然这张图中没有体现出来的有两点:

  1. 为了提升整个HashMap的读取效率,当HashMap中存储的元素大小等于桶数组大小乘以负载因子的时候整个HashMap就要扩容,以减小哈希碰撞,具体细节我们在后文中讲代码会说到
  2. Java 8中如果桶数组的同一个位置上的链表数量超过一个定值,则整个链表有一定概率会转为一棵红黑树。

整体来看,整个HashMap中最重要的点有四个:初始化数据寻址-hash方法数据存储-put方法,扩容-resize方法,只要理解了这四个点的原理和调用时机,也就理解了整个HashMap的设计。

把书读厚

在理解了HashMap的整体架构的基础上,我们可以试着回答一下下面的几个问题,如果对其中的某几个问题还有疑惑,那就说明我们还需要深入代码,把书读厚。

  1. HashMap内部的bucket数组长度为什么一直都是2的整数次幂
  2. HashMap默认的bucket数组是多大
  3. HashMap什么时候开辟bucket数组占用内存
  4. HashMap何时扩容?
  5. 桶中的元素链表何时转换为红黑树,什么时候转回链表,为什么要这么设计?
  6. Java 8中为什么要引进红黑树,是为了解决什么场景的问题?
  7. HashMap如何处理keynull的键值对?

new HashMap()

JDK 8中,在调用new HashMap()的时候并没有分配数组堆内存,只是做了一些参数校验,初始化了一些常量

public HashMap(int initialCapacity, float loadFactor) {
   
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                            initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                            loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

static final int tableSizeFor(int cap) {
   
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

tableSizeFor的作用是找到大于cap的最小的2的整数幂,我们假设n(注意是n,不是cap哈)对应的二进制为000001xxxxxx,其中x代表的二进制位是0是1我们不关心,

n |= n >>> 1;执行后n的值为:

image-20210403000630081

可以看到此时n的二进制最高两位已经变成了1(1和0或1异或都是1),再接着执行第二行代码:

可见n的二进制最高四位已经变成了1,等到执行完代码n |= n >>> 16;之后,n的二进制最低位全都变成了1,也就是n = 2^x - 1其中x和n的值有关,如果没有超过MAXIMUM_CAPACITY,最后会返回一个2的正整数次幂,因此tableSizeFor的作用就是保证返回一个比入参大的最小的2的正整数次幂。

JDK 7中初始化的代码大体一致,在HashMap第一次put的时候会调用inflateTable计算桶数组的长度,但其算法并没有变:

// 第一次put时,初始化table
private void inflateTable(int toSize) {
   
    // Find an power of 2 >= toSize
    int capacity = roundUpToPowerOf2(toSize);
    threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    table = new Entry(capacity);
    initHashSeedAsNeeded(capacity);
}

这里我们也回答了开头提出来的问题:

HashMap什么时候开辟bucket数组占用内存?答案是在HashMap第一次put的时候,无论Java 8还是Java 7都是这样实现的。这里我们可以看到两个版本的实现中,桶数组的大小都是2的正整数幂,至于为什么这么设计,看完后文你就明白了。

hash

HashMap这个特殊的数据结构中,hash函数承担着寻址定址的作用,其性能对整个HashMap的性能影响巨大,那什么才是一个好的hash函数呢?

  • 计算出来的哈希值足够散列,能够有效减少哈希碰撞
  • 本身能够快速计算得出,因为HashMap每次调用getput的时候都会调用hash方法

下面是Java 8中的实现:

static final int hash(Object key) {
   
    int h;
    return (key == null) 
  • 109
    点赞
  • 391
    收藏
    觉得还不错? 一键收藏
  • 28
    评论
回答: HashMapJava的一个常用数据结构,它的底层是由hash数组和单向链表实现的。每个数组元素都是一个链表,通过Node内部类实现了Map.Entry接口来存储键值对。HashMap通过put和get方法来存储和获取数据。\[1\] 在重写equals方法时,我们需要同时重写hashCode方法。这是因为在HashMap,查找value是通过key的hashCode来进行的。当找到对应的hashCode后,会使用equals方法来比较传入的对象和HashMap的key对象是否相同。因此,为了保证正确的查找和比较,我们需要同时重写equals和hashCode方法。\[2\]\[3\] HashMap在什么时候进行扩容呢?当HashMap的元素数量超过了负载因子(默认为0.75)与当前容量的乘积时,就会进行扩容。扩容是为了保持HashMap的性能,因为当元素数量过多时,链表的长度会变长,查找效率会下降。扩容的过程是创建一个新的数组,将原数组的元素重新分配到新数组,然后将新数组替换为原数组。\[3\] #### 引用[.reference_title] - *1* *2* *3* [史上最全Hashmap面试总结,51道附带答案,持续更新...](https://blog.csdn.net/androidstarjack/article/details/124507171)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值