面试腾讯,字节跳动,华为90%会被问到的HashMap!你会了吗?
简介
HashMap是平常使用的非常多的,内部结构是 数组+链表/红黑树 构成,很多时候都是多种数据结构组合。
我们先看一下HashMap的基本操作:

new HashMap(n);
第一个知识点,传入n,构造的HashMap容量就是n吗?
答案是:不一定。
public HashMap(int initialCapacity, float loadFactor) {
this.loadFactor = loadFactor; //负载因子 默认0.75
//设置容量
this.threshold = tableSizeFor(initialCapacity);
}
tableSizeFor 这段代码其实就做了一件事,例如,你初始化给了10,它会给你16,大于10的是2的k次幂。
以初始值50为例,讲一下实现原理:
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;
}
算法就是让二进制不断右移,与自己异或,把第一位为1(最高位)后面全变为1,111111 + 1 = 1000000 = 26 2^62
6
(符合大于50并且是2的整数次幂 )

第二个知识点,回答开题的问题,为什么hash函数这么设计?
HashMap的hash函数是根据Key值计算的;
一定要尽可能降低hash碰撞,越分散越好;
算法一定要尽可能高效,因为这是高频操作;
再来看一下这段代码:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这段代码有个名字,叫扰动函数,大家想一下,如果hash函数直接使用key.hashCode()作为hash 值怎么样?
key.hashCode()获得的是key的hashcode(), 如果HashMap数组长度为16,求对象在数组存储位置 (n - 1) & hash 就相当于 0000 1111 & hash ,让 hash 高位全部置为0,只用到了 hash 的低位,因为只用了低位,碰撞的几率就会比较大。
聪明的算法设计者兼顾性能和降低碰撞,就考虑用高16位和低16位结合起来异或形成hash 值。如下图所示,

第三个知识点,相比1.7,JDK1.8做了哪些优化?
1.7 使用头插法,1.8使用尾插法;
1.7 hash函数使用4次位运算+5次异或,1.8使用1次位运算+1次异或;
1.7 使用数组+链表的结构,1.8 使用数组 + 链表 +红黑树;
1.7 扩容需要对原始元素重新hash & (len -1), 1.8 计算元素新位置 = 原始位置 / 原始位置 + 旧容量;
下面开始解释说的四条:
第一条:
1.8 之前都是使用头插法,因为作者认为现在插入的数据是热乎的,最有可能被立即使用到,所以用头插法;
而为什么1.8用尾插法呢,如果是头插法,在多线程环境下,会出现这样一个问题:A线程在插入节点B,B线程也在插入,遇到容量不够开始扩容,重新hash,放置元素,采用头插法,后遍历到的B节点放入了头部,这样形成了环,如下图所示:

第二条:
1.7的hash 函数如下,可以和上面的对比看:
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
四次无符号右移 五次异或
第三条:
画了一张插入流程图如下:
注意4个点:
先插入新节点再扩容(1.7是先判断容量,不够先扩容再插入);
先判断是否为红黑树,链表插入结束判断是否是否应该转为红黑树;
红黑树转为链表的临界值是6不是8,原因是如果长度经常在8附近,转来转去,浪费资源。
为什么红黑树的阈值是8,因为合理的hash函数,发生碰撞链表长度为8的概率作者计算为千万分之后。

// 作者给的hash冲突链表长度分别为以下值得概率
* 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
第四条:
1.7 扩容后会采用 hash & (len -1)重新计算所有数组元素的位置,但是1.8采用简单快捷的方式定位新位置: 直接放在原位置/ 原位置 + 旧容量
这个怎么理解呢?看下面这张图,

分二种情况:
比如现在 数组长度为16,元素的hash值为0101 , 0000 0101 & 0000 1111 = 0000 0101,
扩容之后,因为高位为0,0000 0101 & 0001 1111 = 0000 0101,位置没变,可以直接放到扩容后的原始位置。
数组长度为16,原始的hash值为 0001 0101, 0001 0101 & 0000 1111 = 0101, 扩容到32之后, 0001 0101 & 0001 1111 = 0001 0101, 比原来的位置大16。
有意思吧! 好好品,越品越有意思!
截取了一段扩容代码
final Node<K,V>[] resize(){
//***
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
面试腾讯,字节跳动,华为90%会被问到的HashMap!你会了吗?的更多相关文章
- 面试利器!字节跳动2021年Android程序员面试指导小册已开源
整份手册分为两个部分,分别是:Java部分.Android部分.数据结构与算法篇.字节跳动2020年全年面试题总结篇! 每个知识点都有左侧导航书签页,看的时候十分方便,由于内容较多,这里就截取一部分图 ...
- 史上最全!2020面试阿里,字节跳动90%被问到的JVM面试题(附答案)
前言:最近老是收到小伙伴的私信问我能不能帮忙整理出一份JVM相关的面试题出来,说自己在大厂去面试的时候这一块问的是特别多的,每次自己学的时候每次都学不到重点去.这不他来了,一份详细的JVM面试真题给大 ...
- 面试阿里,字节跳动90%会被问到的Java异常面试题集,史上最全系列!
Java异常架构与异常关键字 Java异常简介 Java异常是Java提供的一种识别及响应错误的一致性机制. Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程 ...
- 深度分享:面试阿里,字节跳动,美团90%会被问到的HashMap知识
一,HashTable 哈希表,它相比于hashMap结构简单点,它没有涉及红黑树,直接使用链表的方式解决哈希冲突. 我们看它的字段,和hashMap差不多,使用table存放元素 private t ...
- 面试阿里,字节跳动,华为必须知道的Java创建对象的5种方式
Java创建对象的5种方式 1.直接new,调用了构造器2.通过clone(),没有调用构造器3.通过反射,调用了构造器4.通过反序列化,没有调用构造器5.通过Unsafe类的allocateInst ...
- 深度分析:面试阿里,字节跳动,美团90%被问到的List集合,看完还不懂算我输
1 List集合 1.1 List概述 在Collection中,List集合是有序的,可对其中每个元素的插入位置进行精确地控制,可以通过索引来访问元素,遍历元素. 在List集合中,我们常用到Arr ...
- 应聘阿里,字节跳动美团90%会问到的JVM面试题! 史上最全系列!
Java 内存分配 • 寄存器:程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码.• 静态域:static 定义的静态成员.• 常量池:编译时被确定并保存在 .class 文件中的(f ...
- 面试阿里,字节跳动99%会被问到的java线程和线程池,看完这篇你就懂了!
前言: 最近也是在后台收到很多小伙伴私信问我线程和线程池这一块的问题,说自己在面试的时候老是被问到这一块的问题,被问的很头疼.前几天看到后帮几个小伙伴解决了问题,但是问的人有点多我一个个回答也回答不过 ...
- 5年Android程序员面试字节跳动两轮后被完虐,请查收给你的面试指南
大家应该看过很多分享面试成功的经验,但根据幸存者偏差的理论,也许多看看别人面试失败在哪里,对自己才更有帮助. 最近跟一个朋友聊天,他准备了几个月,刚刚参加完字节跳动面试,第二面结束后,嗯,挂了- 所以 ...
随机推荐
- App在后台运行时如何保存数据到sqlite数据库
iOS程序进入后台后,是不允许读写任何文件和数据库(sqlite),但是允许读写NSUserDefault中的数据. 因此在后台时如果想存储数据,则可使用NSUserDefault(偏好设置)临时保存 ...
- 1024|推荐一个开源免费的Spring Boot教程
2020-1024=996! 今天,星期六,你们是否加班了?我反正加了!早上去公司开了一早上会,中午回家写下了这篇文章. 今天,我要推荐一个开源免费的Spring Boot项目,就是我最近日更的Spr ...
- (Pixel2PixelGANs)Image-to-Image translation with conditional adversarial networks
Introduction 1. develop a common framework for all problems that are the task of predicting pixels f ...
- C. Bank Hacking 解析(思維)
Codeforce 796 C. Bank Hacking 解析(思維) 今天我們來看看CF796C 題目連結 題目 略,請直接看原題. 前言 @copyright petjelinux 版權所有 觀 ...
- D. The Wu 解析(思維、二進位運算)
Codeforce 1017 D. The Wu 解析(思維.二進位運算) 今天我們來看看CF1017D 題目連結 題目 略,請直接看原題 前言 官方解答實在看不懂...之後還記得的話再補那個做法吧 ...
- STM32入门系列-使用库函数点亮LED软硬件分析
电路图分析 首先找来单片机的原理图,根据原理图进行相关的设计工作. 例如在上图中相同网络标号表示它们是连接在一起的,因此D1发光二极管阴极是连接在STM32的PC0管脚上,D2指示灯阴极连接在PC1管 ...
- 样式操作-核心DOM
1.核心DOM 只能操作行内(内嵌)样式-->style里面的所有内容(核心DOM) //麻烦获取 console.log(div.getAttribute("style") ...
- Java学习的第五十一天
1.例9.3 析构函数 public class Cjava { public static void main(String[]args) { Student s1=new Student(1001 ...
- 常用的Linux命令,日常收集记录
1.# yum install -y xxxx 解释:install代表往系统中安装一个或者多个软件包:-y 代表回答全部问题为是 2.# ps -ef | grep yum (根据进程名来查看进 ...
- WSL Ubuntu 18.04 LTS + VS Code
WSL Ubuntu 18.04 LTS + VS Code WSL(Windows Subsystem for Linux)使得Windows用户能够在Windows系统上使用原生的Linux环 ...