前言

HashMap作为Java面试中高频出现的面试题,是面试官们最喜欢问的问题之一,通常会出现在前3道技术面试题中,主要是为了筛选不会Java的候选人,亦或者是考察候选人平时会不会看JDK源码,下面我们将从不同维度讲解在面试过程中,我们需要如何回答HashMap有关的面试题,以及对于面试而言,需要掌握到什么程度。

本文只是列举了一些常见且典型,并且需要花时间读源码的面试问题,并没有包括一些显而易见的简单的题,这样能让正在准备面试的你更快更全面的复习。

问题1:HashMap的实现原理

讲到HashMap的原理,大多数程序员都知道HashMap是基于数组加链表,这里我们简单讲一下原理。

我们先介绍一个HashMap内部的数据结构Node,结构很简单,hash字段存的是经过HashMap的hash()方法得出的key的hash值,keyvalue字段分别存的是键值对,next字段存的是下一个Node对象.

static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;// ...
}

我们知道HashMap内部会维护一个Node类型的数组tab,当我们用put(key, value)来存储一个键值对时候,首先会用HashMap内置的hash方法得到这个key的hashcode,然后用这个hash值和数组的长度作逻辑与运算,结果就是该键值对需要存放的数组位置,如下。

// 检查tab[pos]是否已经有node元素了
pos = (tab.length -1) & hash(key)

如果tab[pos]已经有node元素了,说明发生了哈希冲突,那么Node中的next字段就会发挥作用,将哈希值相同的元素连起来变成链表。从这里可以看出Java中的HashMap解决冲突是使用拉链法。常见的解决哈希冲突的三种方法

  • 开放寻址法:当发生哈希冲突时,按照一定次序从哈希表中寻找一个空闲的单元
  • 拉链法:当发生哈希冲突时,将发生冲突的元素会以链表的方式存储
  • 再哈希法:当发生哈希冲突时,会再哈希一次

问题2:HashMap的初始大小是多少

在不指定参数的情况下,初始大小会在第一次插入新元素时指定,Node数组大小为16,扩容阈值是总容量*0.75,初始时是12。

问题3:什么时候需要扩容,每次扩容多少

当发生下面2种情况是将会扩容,每次扩容到原数组大小的2倍

  • 每次发生插入新元素操作时,都会检查HashMap的大小是不是超过了总容量*0.75
  • 当需要把链表转成红黑树时,会先检查总的node数组长度是否小于64,如果是,会先进行扩容而不是转红黑树

问题4:链表什么时候会转成红黑树,转成红黑树有什么好处

当链表的长度第一次大于等于8时,且总的node数组长度大于64时会将链表转成红黑树。当我们扩容后,如果此时红黑树小于等于6个节点,则会转回链表。

我们都知道链表的查询时间复杂度是O(1),而红黑树是O(logN),所以当哈希冲突非常严重时,红黑树的效率要明显好于链表,当然在冲突不严重的时候,比如HashMap中定义的小于8时,链表的空间复杂度要明显优于红黑树,下面的Java Doc也做了相关的解释。

那么为什么会在大于等于8的时候需要转成红黑树呢,Java Doc中给出了这样一段解释:

/**
* Because TreeNodes are about twice the size of regular nodes, we
* use them only when bins contain enough nodes to warrant use
* (see TREEIFY_THRESHOLD). And when they become too small (due to
* removal or resizing) they are converted back to plain bins.  In
* usages with well-distributed user hashCodes, tree bins are
* rarely used.  Ideally, under random hashCodes, the frequency of
* nodes in bins follows a Poisson distribution
* (http://en.wikipedia.org/wiki/Poisson_distribution) with a
* parameter of about 0.5 on average for the default resizing
* threshold of 0.75, although with a large variance because of
* resizing granularity. Ignoring variance, the expected
* occurrences of list size k are (exp(-0.5) * pow(0.5, k) /
* factorial(k)). The first values are:
*
* 0:    0.60653066
* 1:    0.30326533
* 2:    0.07581633
* 3:    0.01263606
* 4:    0.00157952
* 5:    0.00015795
* 6:    0.00001316
* 7:    0.00000094
* 8:    0.00000006
* more: less than 1 in ten million
**/

JDK解释说用红黑树所占用的空间几乎是链表的2倍,所以我们只在有很多哈希冲突的极端的情况下,那为什么当冲突数达到8时会被认为是极端情况呢,Java Doc解释到大多数的hash算法都是均匀分布的,会使用红黑树的场景非常少见。而理论上,一个随机分布的hash算法,元素在某个数组位置出现的概率是服从泊松分布,那么在0.75的扩容因子下,泊松分布的概率参数为0.5,此时发生8个相同hash值的概率为低于一千万分之一,所以一个设计良好的hash算法几乎是不会发生红黑树转换问题的。

问题5:HashMap中的Key是如何hash的

static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

从这段code中可以发现一个有意思的逻辑,即让key的hashCode()方法返回的hash值右移16位后再与hash作逻辑运算,我们知道hashCode()产生的是一个int值,而int值是32位的,当int值右移16位后,其实就是这个hashCode的高位和低位做了异或运算,为什么要这样呢,我们来看一下官方的Java Doc是怎么说的:

/*** Computes key.hashCode() and spreads (XORs) higher bits of hash* to lower.  Because the table uses power-of-two masking, sets of* hashes that vary only in bits above the current mask will* always collide. (Among known examples are sets of Float keys* holding consecutive whole numbers in small tables.)  So we* apply a transform that spreads the impact of higher bits* downward. There is a tradeoff between speed, utility, and* quality of bit-spreading. Because many common sets of hashes* are already reasonably distributed (so don't benefit from* spreading), and because we use trees to handle large sets of* collisions in bins, we just XOR some shifted bits in the* cheapest possible way to reduce systematic lossage, as well as* to incorporate impact of the highest bits that would otherwise* never be used in index calculations because of table bounds.
*/

翻译过来就是说,如果不同key的hashcode只在高位有差异的话,那么他们将会发生哈希冲突,为什么会这样呢,因为大多数情况下我们的HashMap的容量都不会非常大,而hash后的值是需要与HashMap的Node数组长度作逻辑与操作的,如果HashMap的数组长度很小,那么每次逻辑与操作都会丢失hashCode的高位值,从而导致哈希冲突。而高低位的异或运算是一种权衡之计,即解决了hash分布问题,又不太影响性能,在极端情况下还有红黑树来解决冲突。

问题6:为什么HashMap总是以2次幂来进行扩容

pos = (tab.length -1) & hash(key)

因为我们决定放在数组的哪个位置是使用上面的逻辑与的方法,tab.length是2次幂转成二进制就是1后面n个0,tab.length-1就是n个1,那么在做完逻辑与后,不同hash值得key所在的位置就是hash值本身所代表的位置,不会因为这个逻辑与而导致不同的hash的key在相同的位置。

而扩容之后,由于tab.length只是从n个1变成了n+1个1,那么原来hash值在n+1位前都相同的key还会在同一位置,而n+1位不同的key将会移到原来的2倍位置。

问题7:HashMap的线程安全版本有哪些,各自的使用场景

  • Collections.synchronizedmap:通过将所有线程不安全的方法加上synchronized关键字实现,当我们只是需要数据一致性时可以使用这个实现
  • ConcurrentHashMap:HashMap的线程安全版本,支持并发读,所以当我们需要频繁读写操作时,ConcurrentHashMap的性能优于Collections.synchronizedmap

结语

从HashMap可以扩展出很多面试题,例如并发问题,集合问题,队列问题等,这些将在后面的相关专题中一一介绍。

Java HashMap面试须知相关推荐

  1. 阿里 + 京东 Java 岗面试题概要(面试须知)

    很多程序员都会担心 35 岁的职业危机,而数据也显示,40 岁以上的程序员几乎不存在,大都转了管理岗,余下的只能被迫离职或者转行.然而,太久待在舒适区,可能连小公司的面试,都很难通过了. 程序员是最需 ...

  2. 阿里 + 京东 Java 岗面试题概要(面试须知

    Zookeeper 做服务的注册中心,如果服务规模大于 1000,会发生羊群效应网络风暴,怎么优化? 分布式事务两阶段提交如果第二阶段超时了怎么办? 从源码角度讲一讲 Eureka 的底层架构与设计原 ...

  3. 多线程面试题_100多线程和Java并发面试问答–最终清单(PDF下载)

    多线程面试题 在这篇文章中,我们将提供有关多线程和Java并发面试问答的综合文章. 编者注:并发始终是开发人员的挑战,编写并发程序可能非常困难. 引入并发时,有很多事情可能会崩溃,并且系统的复杂性会大 ...

  4. 【Java核心面试宝典(1),程序员Javaweb源码

    **这里就涉及到了一个链表中数据存储时,进行"树化"和"链化"的一个过程,**那么什么是"树化"和"链化"呢? 当我们在 ...

  5. 一次违反常规的Java大厂面试经历,内含福利

    分享第一份Java基础-中级-高级面试集合 Java基础(对象+线程+字符+接口+变量+异常+方法) Java中级开发(底层+Spring相关+Redis+分布式+设计模式+MySQL+高并发+锁+线 ...

  6. Java HashMap工作原理深入探讨

    大部分Java开发者都在使用Map,特别是HashMap.HashMap是一种简单但强大的方式去存储和获取数据.但有多少开发者知道 HashMap内部如何工作呢?几天前,我阅读了java.util.H ...

  7. java工程师面试如何自我介绍

    首先进行自我介绍,如姓名.籍贯.学历,毕业院校: 接下来介绍工作情况,如:在哪工作过多久:工作内容是什么: 之后介绍下专业技能,挑选强项说明(切勿刚接触,之后乱说,否则面试者一问,马上穿帮). 备注: ...

  8. Java基础面试16问

    来自三太子敖丙的Java基础面试16问 原文地址 ** 说说进程和线程的区别? ** 进程是程序的一次执行,是系统进行资源分配和调度的独立单位,他的作用是是程序能够并发执行提高资源利用率和吞吐率. 由 ...

  9. 美团科技 Java工程师_美团网java工程师面试都会问哪些问题?

    美团网java工程师面试主要考察面试者的专业知识,涉及TCP/IP 线程.synHashMap底层.进程关系.servlet生命周期.pringMVC单例异常.Object方法.ConcurrentH ...

最新文章

  1. 四种类型的数据分析模式
  2. Flink 1.7.2 dataset transformation 示例
  3. ASP.NET MVC中如何在客户端进行必要的判断
  4. 从0到1搭建spark集群---企业集群搭建
  5. oracle中如何取消外键的,ORACLE中添加删除主键、外键
  6. 电脑遇到脱机状态怎么解除?
  7. java程序员推荐书籍
  8. json java 比较_Java中json工具对比分析
  9. 期刊论文发表有哪些方法
  10. 程序员面试智力题(六)
  11. h5/web遮罩弹窗
  12. HFDS 内部工作机制
  13. UGUI源码分析:GridLayoutGroup网格布局组件与ContentSizeFitter尺寸调节组件
  14. 模板--二分图最大匹配
  15. python循环引用的解决办法
  16. Oxygen PDF Chemistry转换场景的处理器
  17. python模拟鼠标键盘点击,简单自动化动物餐厅
  18. nosql | 搭建mongodb副本集
  19. 12864c语言接温度计,单片机万年历+温度计+12864LCD液晶显示 仿真+程序
  20. linux svn e170001,jenkins - svn: E170001报错的原因以及解决方案

热门文章

  1. 进销存设计中的库存设计
  2. 3万行代码硬撸一个一键发布文章工具,简直不要太好用,从此写文章,发文章,太简单了好伐
  3. 若说耳机世界里有一股清流,那这款QCY耳机肯定是其中之一
  4. 【Java基础】Java综合练习
  5. 三款免费杀毒软件+clamAV
  6. 【U盘主控芯片的优缺点】
  7. QT 5.7 for iOS Xcode 8 Project ERROR: Xcode not set up properly. You may need to confirm the license
  8. C语言字符意思 char,c语言中char* 代表什么
  9. 《Effective Java》读书笔记五(枚举和注解)
  10. vue全家桶_vue脚手架