简介

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!你会了吗?的更多相关文章

  1. 面试利器!字节跳动2021年Android程序员面试指导小册已开源

    整份手册分为两个部分,分别是:Java部分.Android部分.数据结构与算法篇.字节跳动2020年全年面试题总结篇! 每个知识点都有左侧导航书签页,看的时候十分方便,由于内容较多,这里就截取一部分图 ...

  2. 史上最全!2020面试阿里,字节跳动90%被问到的JVM面试题(附答案)

    前言:最近老是收到小伙伴的私信问我能不能帮忙整理出一份JVM相关的面试题出来,说自己在大厂去面试的时候这一块问的是特别多的,每次自己学的时候每次都学不到重点去.这不他来了,一份详细的JVM面试真题给大 ...

  3. 面试阿里,字节跳动90%会被问到的Java异常面试题集,史上最全系列!

    Java异常架构与异常关键字 Java异常简介 Java异常是Java提供的一种识别及响应错误的一致性机制. Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程 ...

  4. 深度分享:面试阿里,字节跳动,美团90%会被问到的HashMap知识

    一,HashTable 哈希表,它相比于hashMap结构简单点,它没有涉及红黑树,直接使用链表的方式解决哈希冲突. 我们看它的字段,和hashMap差不多,使用table存放元素 private t ...

  5. 面试阿里,字节跳动,华为必须知道的Java创建对象的5种方式

    Java创建对象的5种方式 1.直接new,调用了构造器2.通过clone(),没有调用构造器3.通过反射,调用了构造器4.通过反序列化,没有调用构造器5.通过Unsafe类的allocateInst ...

  6. 深度分析:面试阿里,字节跳动,美团90%被问到的List集合,看完还不懂算我输

    1 List集合 1.1 List概述 在Collection中,List集合是有序的,可对其中每个元素的插入位置进行精确地控制,可以通过索引来访问元素,遍历元素. 在List集合中,我们常用到Arr ...

  7. 应聘阿里,字节跳动美团90%会问到的JVM面试题! 史上最全系列!

    Java 内存分配 • 寄存器:程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码.• 静态域:static 定义的静态成员.• 常量池:编译时被确定并保存在 .class 文件中的(f ...

  8. 面试阿里,字节跳动99%会被问到的java线程和线程池,看完这篇你就懂了!

    前言: 最近也是在后台收到很多小伙伴私信问我线程和线程池这一块的问题,说自己在面试的时候老是被问到这一块的问题,被问的很头疼.前几天看到后帮几个小伙伴解决了问题,但是问的人有点多我一个个回答也回答不过 ...

  9. 5年Android程序员面试字节跳动两轮后被完虐,请查收给你的面试指南

    大家应该看过很多分享面试成功的经验,但根据幸存者偏差的理论,也许多看看别人面试失败在哪里,对自己才更有帮助. 最近跟一个朋友聊天,他准备了几个月,刚刚参加完字节跳动面试,第二面结束后,嗯,挂了- 所以 ...

随机推荐

  1. Linux创建用户时让每个用户家目录中自带说明文档

    新用户创建时模板文件放在/etc/skel中,只要在skel中放入相应文档,即可在创建用户时在其家目录中产生对应文档 [00:30:48 root@C8[ ~]#ll -a /etc/skel/ ## ...

  2. Linux显示系统信息sh脚本

    #!/bin/bash # #******************************************************************** #Author: wangxia ...

  3. Jmeter入门(1)- 什么是Jmeter以及Jmeter的安装和环境配置

    一. Jmeter简介 Jmeter时Apacha公司使用Java平台开发的一款测试工具 二. Jmeter可以做什么 Jmeter可以用来做接口测试.性能测试.压力测试.数据库测试.Java程序测试 ...

  4. JMeter性能测试工具使用入门

    目录 安装 下载安装包 解压 添加到环境变量 启动 切换中文 打开日志查看 使用示例 创建线程组 添加HTTP接口 添加察看结果树 运行测试 添加断言 添加自定义变量 JMeter是一款强大的性能测试 ...

  5. H5页面字体设置

    H5页面不支持 MicrosoftYaHei(微软雅黑)别傻傻的设置微软雅黑字体了 如果一定要微软雅黑操作如下 @font-face 定义为微软雅黑字体并存放到 web 服务器上,在需要使用时被自动下 ...

  6. 并发编程——多线程计数的更优解:LongAdder原理分析

    前言 最近在学习ConcurrentHashMap的源码,发现它采用了一种比较独特的方式对map中的元素数量进行统计,自然是要好好研究一下其原理思想,同时也能更好地理解ConcurrentHashMa ...

  7. U138097 小鱼吃大鱼 埃氏筛

    题目描述 小P同学在养殖一种非常凶狠的鱼,而且与其他鱼类不同,这种鱼越大越温顺,反而小鱼最凶残.当两条鱼相遇时, 小鱼会不断撕咬大鱼,每一口都咬下与它自身等重的肉(小鱼保持体重不变),直到大鱼的体重小 ...

  8. linux系统中firewalld防火墙管理工具firewall-config(GUI图形用户界面)

    firewall-config是firewalld防火墙管理工具的GUI(图形用户界面)版本,几乎可以实现所有以命令行来执行的操作. firewall-config的界面如下图(在终端直接运行fire ...

  9. SQL Server 列存储索引 第四篇:实时运营数据分析

    实时运营数据分析(real-time operational analytics )是指同时在同一张数据表上执行分析处理和业务处理.分析查询主要是对海量数据执行聚合查询,而事务主要是指对数据表进行少量 ...

  10. Java学习的第二十九天

    1. 如果类中的某个属性不希望被序列化则需要transient关键字 序列化一组对象 2.无问题 3.明天学习打印流