为什么不管大厂还是小厂,面试总是要提到 HashMap?(下)

简介: 不知道大家最近有没有刷过 Java 面试题,有没有发现几乎所有面试题或多或少都会包括 HashMap 面试题。 为什么 Java 中小小的一个 HashMap,值得在面试中被反复提到? 阿粉认为是因为 HashMap 太重要了,大家回看一下自己的业务代码,是不是都有在使用 HashMap ? 即使你真的没有直接使用,但是你使用的一些中间件,或者一些开源框架,这些代码肯定使用 HashMap 完成相关逻辑。 而 HashMap 包含很多核心知识点,从这些知识点可以考察出一个面试者基本知识掌握情况。

寻址算法

当我们计算得到 Hash 值以后,我们还需要计算这个 Hash 值在数组中的位置。

简单的方式我们可以直接使用求模的方式定位,比如

hash=1000
table.size=16
n=hash%table.size=8

但是 HashMap 采用的是另外一种更为精妙的算法:

40.jpg

这种方式等同求模法,但是计算性能会更好,但是这里我们需要注意了,这种方式前提为 n 也就是数组的长度必须为 2 的幂次方

这里阿粉就不具体计算了,感兴趣的同学可以网上查找一下推导过程

面试题

为什么 HashMap 扩容都是 2 的幂次方?

看完这个,大家这个面试题了吧。

扩容

当 HashMap 元素过多时,这时必须扩容,从而保证 HashMap 查找性能。

扩容过程,我们就需要涉及以下几个核心参数:

  • 扩展因子:loadFactor
  • 实际元素数量:size
  • 数组长度:capacity
  • 扩容的阈值大小:threshold=capacity*loadFactor

当 HashMap 中元素数量大于 threshold,HashMap 就会开始扩容。

JDK1.7 扩容的时候,HashMap 每个元素将会重新计算 Hash 值,然后使用寻址算法,查找新的位置。

41.jpg

在 JDk1.8 中,采用了一种更精妙的算法:

42.jpg

其使用 e.hash & oldCap == 0, 元素要么放在原位置,要么放在原位置+原数组长度。

这里解释起来比较复杂,这里阿粉就不再详细展开,感兴趣同学可以自行查找一下相关文章。

面试题

问:加载因子为什么 0.75,而不是其他值?

答:可以说是一个经过考量的经验值。加载因子涉及扩容,下次扩容的阈值=数组桶的大小*加载因子,如果加载因子太小,这就会导致阈值太小,这就会导致比较容易发生扩容。

如果加载因子太大,那就会导致阈值太大,可能冲突会很多,导致查找效率下降。

多线程并发

好了,终于到到了最后一个知识点,多线程并发。

HashMap 在多线程并发情况下会怎么样?

这里我们需要分 JDK1.7 与 JDK1.8 来讲。

在 JDK1.7 中,由于扩容迁移时采用了头插法,从而将会导致产生死链。

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            // 以下代码导致死链的产生
           e.next = newTable[i];
            // 插入到链表头结点,
            newTable[i] = e;
            e = next;
        }
    }
}


43.jpg

而一旦产生死链,极有可能导致程序陷入死循环,从而导致 CPU 使用率上升。

JDK1.8 中使用尾插法,从而解决这个问题,但是依然还会存在相关问题。

比如:

并发赋值时被覆盖

44.jpg

并发的情况下,一个线程的赋值可能被另一个线程覆盖,这就导致对象的丢失。

size 计算问题


45.jpg

img

每次元素增加完成之后,size 将会加 1。这里采用 ++i方法,天然的并发不安全。

面试题

关于并发,这里可以提到很多面试题。

可以是线程相关的,也可以是并发编程相关。

不过如果面试官既然已经提到这里,我们可以试着将他引导他如何解决 HashMap 并发编程的问题,从而我们下面开始回答出 ConcurrentHashMap

最后

经过上面一顿分析,我们可以看到小小一个 HashMap 其实涉及到很多知识点,这些点拆开来讲就可以变成一道道面试题。

另外这些点在平常编程的过程中也要特别注意,一不小心我们就会踩一个大坑。

今天这篇文章主要提及一下,HashMap 涉及的知识点,所以阿粉没有过多深入的分析,这里感兴趣的同学可以在深入学习准备一下。


相关文章
|
2月前
|
存储 算法 Java
【Java集合类面试八】、 介绍一下HashMap底层的实现原理
HashMap基于hash算法,通过put和get方法存储和获取对象,自动调整容量,并在碰撞时用链表或红黑树组织元素以优化性能。
|
2月前
|
Java 索引
【Java集合类面试九】、介绍一下HashMap的扩容机制
HashMap的扩容机制包括初始容量16,以2的次方进行扩充,使用负载因子0.75判断是否扩容,以及链表长度达到阈值时转换为红黑树,以优化性能。
【Java集合类面试九】、介绍一下HashMap的扩容机制
|
2月前
|
存储 Java
【Java集合类面试七】、 JDK7和JDK8中的HashMap有什么区别?
JDK7中的HashMap使用数组加链表解决冲突,而JDK8增加了红黑树结构以优化链表过长时的性能,提高查找效率。
|
2月前
|
安全 Java
【Java集合类面试十五】、说一说HashMap和HashTable的区别
HashMap和Hashtable的主要区别在于Hashtable是线程安全的,不允许null键和值,而HashMap是非线程安全的,允许null键和值。
|
2月前
|
安全 Java
【Java集合类面试十三】、HashMap如何实现线程安全?
实现HashMap线程安全的方法包括使用Hashtable类、ConcurrentHashMap,或通过Collections工具类将HashMap包装成线程安全的Map。
|
2月前
|
Java
【Java集合类面试十一】、HashMap为什么用红黑树而不用B树?
HashMap选择使用红黑树而非B树,是因为红黑树在内存中实现简单,节点更小,占用内存少,且在插入、删除和查找操作上提供更好的平衡性能。
|
2月前
|
安全 Java
【Java集合类面试十六】、HashMap与ConcurrentHashMap有什么区别?
HashMap是非线程安全的,而ConcurrentHashMap通过减少锁粒度来提高并发性能,检索操作无需锁,从而提供更好的线程安全性和性能。
|
2月前
|
Java
【Java集合类面试十四】、HashMap是如何解决哈希冲突的?
HashMap解决哈希冲突的方法是通过链表和红黑树:当链表长度超过一定阈值时,转换为红黑树以提高性能;当链表长度缩小到另一个阈值时,再转换回链表。
|
2月前
|
Java
【Java集合类面试十二】、HashMap为什么线程不安全?
HashMap在并发环境下执行put操作可能导致循环链表的形成,进而引起死循环,因而它是线程不安全的。
|
2月前
|
存储 Java
【Java集合类面试十】、HashMap中的循环链表是如何产生的?
在多线程环境下,HashMap在扩容时如果发生条件竞争,元素的插入顺序可能形成循环链表,导致死循环。
下一篇
无影云桌面