Jdk_HashMap 源码 —— hash(Object)
Jdk 源码
HashMap 的源码是在面试中考的算是比较多的,其中有很多高性能的经典写法,也值得多学习学习。
本文是本人在阅读和学习源码的过程中的笔记(不是教程),如有错误欢迎指正。
Jdk Version : 8
System : windows
HashMap 之 hash(Object)
HashMap 的源码内容很多,知识点也特别的多,一篇文章肯定写不完。这里只讨论下 hash(Object) 这个方法的实现,其他的部分参考其他文章。
在 jdk8 中,向 HashMap 中存值的啥时候会调用 put(Key , Value) ,使用案例如下:
// key value
new HashMap<>().put("zhangsan",new Person());
HashMap 的源码如下:
// jdk8 java.util.HashMap 源码第 611-613 行
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
源码中,在 putVal() 之前,会先调用一下 hash(Object) ,他的传参是 key,此方法的作用是根据 key 值,生成一个数,以此来确认这对要储存的 key-value 放在数组的哪个位置(HashMap的底层数据结构:参考2),并且尽量要让不同的 key 生成的数据重复的概率小,而且不会相差范围太大。
其源码如下:
// jdk8 java.util.HashMap 源码第 337-340行
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
源码中 key == null 的时候返回 0 ,由此可见 HashMp 的 key 是可以为 null 的。
上面的源码其实挺精简的,我们先把它拆分一下:
static final int hash(Object key) {
int h;
if(key == null){
h = 0;
}
else{
// 第一步
int h1 = key.hashCode();
// 第二步
int h2 = h1 >>> 16;
// 第三步
h = h1^h2;
}
return h;
}
- 第一步:
第一步调用的是一个继承自 Object 的本地方法 public native int hashCode(); 此方法会根据对象生成一个 int 类型的 hash 值,而且同一个对象在同一个环境上每次生成的也都相同。这个方法在 jdk8 上是用 c/c++ 实现的,所以在不同的平台上可能实现的方法也有所不同(一般会根据线程或者对象的内存地址之类的信息来生成)。
public static void main(String[] args) {
Object o = new Object();
System.out.println(o.hashCode());
// 输出:1915503092
System.out.println(o.hashCode());
// 输出:1915503092
}
理论上,得到的 hash 值范围是 -2147483648 到 2147483647 正负加起来有40多亿个这么多,所以不同的两个对象生成同一个 hash 值概率是很小很小的。
但是,如果此时直接将生成的这个 hash 值作为 HashMap 数组的下标的话,那么数组的大小就要40多亿,这显然是不行的(HashMap中数组的初始大小才 16 )。
于是,下面就要对这个这数进行处理,在尽量不损失所有信息的情况下,将这个数的范围缩小。
- 第二步:
假设我们上面生成的 hash 值是 4294963434 ,它的的二进制是 1111 1111 1111 1111 1111 0000 1110 1010。
将这个的二进制进行“无符号右移” 16 位,得到 0000 0000 0000 0000 1111 1111 1111 1111 ,这样我们得到的这个值就是将 hash 值的高 16 位的信息移动到低 16 位上。
如下图:

- 第三步
将 h1 与 h2 做“异或”操作 h1 ^ h2。相当于现在低 16 位上即包含了原来的高 16 位信息又包含了原来低 16 位信息。这样做混合了高低位的数据,就将它的随机性尽可能的缩小到 16 位,我们只要用到 hash 的低 16 位就可以了。如下:

但是这个 h 依然很大,还是不能能够直接作为数组的下标。
- 再次处理 hash 值
别急,这里只看了 hash(Object key) 这个方法,我们回到 put 方法的源码。
// jdk8 java.util.HashMap 源码第 611-613 行
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
进到 putval 这个方法里。
// jdk8 java.util.HashMap 源码第 625-666行
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 主要看这里
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
// 此处省略部分源码...
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
- tab:就是储存数据的额数组。
- n:是数组的大小。
上面的源码中,对计算得来的 hash 值进行了一次计算 i = (n - 1) & hash。计算得到的 i 也就是 HashMap 中的数组下标了。
HashMap 的数组初始化大小是 16,16 - 1 = 15 ,换成二进制就是 0000 0000 0000 0000 0000 0000 0000 1111,所以计算“与”操作,就只保留了 hash 的最后四位,得到 5 就是数组的下标。

数组的长度 n 。 n - 1 与 hash 做“与”操作,也就把高位都归0了,结果的取值范围也刚好就是 0 到 n 。这个前提是 n 是 2 的整次幂。
这也解释了为什么很多面试都喜欢问的问题:“为什么HashMap 的数组长度要取整数幂?”
这样,随着 HashMap 的数组变大,碰撞的 key 值的冲突可能性理论上应该是越来越低的,同时在数组很小的时候,碰撞的概率也不是很大(因为地位混合了高位的随信息)。
至此。
参考
1、JDK源码中HashMap的hash方法原理是什么?(这个大佬写的是真的好)。
2、HashMap底层数据结构(数组+链表+红黑树)
Jdk_HashMap 源码 —— hash(Object)的更多相关文章
- Java源码之Object
本文出自:http://blog.csdn.net/dt235201314/article/details/78318399 一丶概述 JAVA中所有的类都继承自Object类,就从Object作为源 ...
- JDK1.8源码学习-Object
JDK1.8源码学习-Object 目录 一.方法简介 1.一个本地方法,主要作用是将本地方法注册到虚拟机中. private static native void registerNatives() ...
- 【java基础之jdk源码】Object
最新在整体回归下java基础薄弱环节,以下为自己整理笔记,若有理解错误,请批评指正,谢谢. java.lang.Object为java所有类的基类,所以一般的类都可用重写或直接使用Object下方法, ...
- java源码阅读Object
1 类注释 Class {@code Object} is the root of the class hierarchy. Every class has {@code Object} as a s ...
- Java读源码之Object
前言 JDK版本: 1.8 最近想看看jdk源码提高下技术深度(比较闲),万物皆对象,虽然Object大多native方法但还是很重要的. 源码 package java.lang; /** * Ja ...
- JDK源码阅读--Object
在java.lang包下 Object类:是所有类的基类(父类) public final native Class<?> getClass(); 返回这个Object所代表的的运行时类 ...
- Java源码分析 | Object
本文基于 OracleJDK 11, HotSpot 虚拟机. Object 定义 Object 类是类层次结构的根.每个类都有 Object 类作为超类.所有对象,包括数组等,都实现了这个类的方法. ...
- yii2 源码分析 object类分析 (一)
转载请注明链接http://www.cnblogs.com/liuwanqiu/p/6737327.html yii2基本上所有的类都是继承的object类,下面就来分析一下object类吧 obje ...
- JDK源码笔记--Object
public final native Class<?> getClass(); public native int hashCode(); public boolean equals(O ...
- 源码学习-Object类
1.Object类是Java所有类的超类 2.查看Object的属性和方法,发现Object类没有属性,只有13个方法,其中7个本地方法. 3.接下来看具体的方法 3.1 Object() 默认的构造 ...
随机推荐
- Redis从入门到放弃(5):事务
1.事务的定义 Redis的事务提供了一种"将多个命令打包, 然后一次性.按顺序地执行"的机制. redis事务的主要作用就是串联多个命令防止别的命令插队. 但是,事务并不具有传统 ...
- .NET爬取美图官网首页数据实战
前言: 在当今信息化社会,网络数据分析越来越受到重视.而作为开发人员,掌握一门能够抓取网页内容的语言显得尤为重要.在此篇文章中,将分享如何使用 .NET构建网络抓取工具.详细了解如何执行 HTTP 请 ...
- 带你读论文丨Fuzzing漏洞挖掘详细总结 GreyOne
本文分享自华为云社区<[论文阅读] (03) 清华张超老师 - Fuzzing漏洞挖掘详细总结 GreyOne>,作者: eastmount. 一.传统的漏洞挖掘方法 演讲题目: 数据流敏 ...
- What is TLS ?
TLS intrduction TLS是一种保障数据传输安全的一种技术(方案),它在传输层工作. 想知道TLS和SSL的关系,先看看他们的历史: 人们遇到数据传输安全问题 1994 SSL 1.0 1 ...
- mac安装nvm
系统:macos catalina版本:10.15.7 一.安装nvm 打开终端执行这个命令 安装的版本是 v0.39.1 curl -o- [https://raw.githubuserconten ...
- 4399 Flash游戏专用浏览器, 无需安装Flash插件
目前所有的主流浏览器都已经不再支持Flash了,即使有一些国内浏览器还支持flash,但只能安装国内特供版Flash Player. 但问题的关键在于,这个国内特供版跟 Adobe 海外发行的版本是两 ...
- 织梦tag怎么显示每个tag相应的文章数量
有些时候我们想实现类似于wordpress那样的tag,就是在显示tag的链接和tag名的同时,还能显示每个tag关联的文章的数量.如下图所示: 这就需要修改/include/taglib/tag.l ...
- 「codeforces - 1344D」Résumé Review
link. 有点狗,但还算个好题. 设定 \(f_i(x)=a_ix-x^3\),\(\Delta_i(x)=f_i(x)-f_i(x-1)\),可以洞察到 \(\Delta_i(x)\) 在正自然数 ...
- RabbitMQ保姆级教程最佳实践
一.消息队列介绍 1.消息队列概念 1.MQ全称为Message Queue,消息队列(MQ)是⼀种应⽤程序对应⽤程序的通信⽅法. 应⽤程序通过读写出⼊队列的消息(针对应⽤程序的数据)来通信,⽽⽆需专 ...
- 使用SpringBoot开发一个POST接口
SpringBoot项目的分层 SpringBoot框架项目一般分为五层: View层:向用户展示页面 Controller层:前后端交互层,接收前端请求,调用Service层中的方法,接收Servi ...