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() 默认的构造 ...
随机推荐
- Django: Token分发
Django后台token分发 在settings.py中引入 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'd ...
- Cannot use v-for on stateful component root element because it renders multiple elements.
<template name:trailerStars> <image v-for="yellow in yellowScore" src="../st ...
- docker网络 bridge 与overlay 模式
转载请注明出处: 1.bridge网络模式 工作原理: 在Bridge模式中,Docker通过创建一个虚拟网络桥接器(bridge)将容器连接到主机上的物理网络接口.每个容器都会被分配一个IP地址, ...
- 形象谈JVM-第一章-认识JVM
对jvm的历史不做过多介绍,感兴趣的同学可以去自行搜索. 我们直接以HotSpot VM(Virtual Machine)举例. why 为什么要有虚拟机? 举一个形象的例子:手机现在几乎是人手一台 ...
- Unity UGUI的Slider(滑动条)件组的介绍及使用
Unity UGUI的Slider(滑动条)件组的介绍及使用 1. 什么是Slider组件? Slider(滑动条)是Unity UGUI中的一种常用UI组件用,于在用户界面中实现滑动选择的功能.通过 ...
- CodeForces 1408D Searchlights
题意 在二维平面有\(n\)个海盗,\(m\)个探照灯,你有两种操作 将所有海盗往上走一步 将所有海盗往右走一步 设海盗为\((a_i,b_i)\),探照灯为\((c_j,d_j)\),当且仅当\(a ...
- 性能调优 session 1 - 计算机体系结构 量化研究方法
近期本人参与的存储系统项目进入到性能调优阶段,当前系统的性能指标离项目预期目标还有较大差距.本人一直奉行"理论指导下的实践",尤其在调试初期,更要抓住主要矛盾,投入最少的资源来获取 ...
- 2016A
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm& ...
- 「luogu - P3911」最小公倍数之和
link. Denote \(cnt_{x}\) = the number of occurrences of \(x\), \(h\) = the maximum of \(a_i\), there ...
- 【matplotlib 实战】--堆叠面积图
堆叠面积图和面积图都是用于展示数据随时间变化趋势的统计图表,但它们的特点有所不同.面积图的特点在于它能够直观地展示数量之间的关系,而且不需要标注数据点,可以轻松地观察数据的变化趋势.而堆叠面积图则更适 ...