面试腾讯,字节跳动,华为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程序员面试字节跳动两轮后被完虐,请查收给你的面试指南
大家应该看过很多分享面试成功的经验,但根据幸存者偏差的理论,也许多看看别人面试失败在哪里,对自己才更有帮助. 最近跟一个朋友聊天,他准备了几个月,刚刚参加完字节跳动面试,第二面结束后,嗯,挂了- 所以 ...
随机推荐
- 基于web的图书管理系统设计与实现(附演示地址)
欢迎访问博主个人网站,记得收藏哦,点击查看 - - - >>>> 公众号推荐:计算机类毕业设计系统源码,IT技术文章分享,游戏源码,网页模板 小程序推荐:网站资源快速收录--百 ...
- gethub网址链接
https://github.com/fraser-in-school/suduku 之前这个网址是手动提交的,现在从新更新下,这是用Visio studio提交的,应该不会缺少并不必要的文件 htt ...
- Ignite、Vertx
Ignite IpFinder 默认采用multicast的ip发现方式 优点: 集群较小时,配置方便 缺点 集群较大100s-1000s时,广播非常耗时,此时建议使用ZooKeeper发现机制(Zo ...
- 【Flutter 混合开发】与原生通信-EventChannel
Flutter 混合开发系列 包含如下: 嵌入原生View-Android 嵌入原生View-iOS 与原生通信-MethodChannel 与原生通信-BasicMessageChannel 与原生 ...
- [Luogu P4215] 踩气球 (线段树)
题面 传送门:https://www.luogu.org/problemnew/show/P4215 Solution 这题十分有意思. 首先,我们可以先想想离线做法,因为在线做法可以从离线做法推出. ...
- Charles使用part3——安装证书&手机抓取https请求
一.配置 Charles 根证书 1.进入 Charles->Help->SSL Proxying->Install Charles Root Certificate ,会打开证书, ...
- Nodejs在VSCode下代码智能提示
在学习Nodejs的过程中发现vscode下默认没有提示,在网上也测试了传统的一些方法,都不好用,最后找到这个npm install --save-dev @types/node
- Fira Code字体安装与配置
俗话说,工欲善其事,必先利其器.算法固然重要,但真正实践也很重要. 一个字体的好看程度,直接决定了写代码和看代码的心情.比如这样: 代码1: #include <iostream> #in ...
- centos 6.5 时间网络同步
安装 ntpdate sudo yum -y install ntp ntpdate 修改为上海时区 sudo vim /etc/sysconfig/clock ZONE = "Asia/S ...
- Python UnboundLocalError: local variable 'xxx' referenced before assignment 解决方法
一.报错含义: val=9 def test(): print(val) val = 6 print(val) test() 翻译:本地变量xxx引用前没有定义. 二.报错原因 这是Python变量作 ...