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)的更多相关文章

  1. Java源码之Object

    本文出自:http://blog.csdn.net/dt235201314/article/details/78318399 一丶概述 JAVA中所有的类都继承自Object类,就从Object作为源 ...

  2. JDK1.8源码学习-Object

    JDK1.8源码学习-Object 目录 一.方法简介 1.一个本地方法,主要作用是将本地方法注册到虚拟机中. private static native void registerNatives() ...

  3. 【java基础之jdk源码】Object

    最新在整体回归下java基础薄弱环节,以下为自己整理笔记,若有理解错误,请批评指正,谢谢. java.lang.Object为java所有类的基类,所以一般的类都可用重写或直接使用Object下方法, ...

  4. java源码阅读Object

    1 类注释 Class {@code Object} is the root of the class hierarchy. Every class has {@code Object} as a s ...

  5. Java读源码之Object

    前言 JDK版本: 1.8 最近想看看jdk源码提高下技术深度(比较闲),万物皆对象,虽然Object大多native方法但还是很重要的. 源码 package java.lang; /** * Ja ...

  6. JDK源码阅读--Object

    在java.lang包下 Object类:是所有类的基类(父类) public final native Class<?> getClass(); 返回这个Object所代表的的运行时类 ...

  7. Java源码分析 | Object

    本文基于 OracleJDK 11, HotSpot 虚拟机. Object 定义 Object 类是类层次结构的根.每个类都有 Object 类作为超类.所有对象,包括数组等,都实现了这个类的方法. ...

  8. yii2 源码分析 object类分析 (一)

    转载请注明链接http://www.cnblogs.com/liuwanqiu/p/6737327.html yii2基本上所有的类都是继承的object类,下面就来分析一下object类吧 obje ...

  9. JDK源码笔记--Object

    public final native Class<?> getClass(); public native int hashCode(); public boolean equals(O ...

  10. 源码学习-Object类

    1.Object类是Java所有类的超类 2.查看Object的属性和方法,发现Object类没有属性,只有13个方法,其中7个本地方法. 3.接下来看具体的方法 3.1 Object() 默认的构造 ...

随机推荐

  1. 检测文件的格式——chardet模块

    f3 = open(file=path,mode='rb') data = f3.read() # print(data) f3.close() result = chardet.detect(dat ...

  2. 《UNIX 传奇:历史与回忆》读后感

    <UNIX 传奇:历史与回忆> 是 bwk(Brian W. Kernighan)2019 年的新作,回忆了 UNIX 在大半个世纪的风雨历程,是一本引人入胜的书籍.通过对 UNIX 操作 ...

  3. Rollup 编译资源离不开 plugin

    rollup 也是一个 JavaScript 的模块化编译工具,可以帮助我们处理资源. 与webpack比较 rollup相比 webpack 理念更为简单,能处理的场景也更有限. 资源类型 处理方式 ...

  4. selenium报错:This version of ChromeDriver only supports Chrome version 109 Current browser version is 112.0.5615.49...解决办法

    前言:跟GPT交互,让其写一段代码,执行失败.经过排查验证,GPT写的代码没有问题,是本地环境问题. 执行报错: selenium.common.exceptions.SessionNotCreate ...

  5. 《SQL与数据库基础》19. 日志

    目录 日志 错误日志 二进制日志 日志格式 日志查看 日志删除 查询日志 慢查询日志 本文以 MySQL 为例 日志 错误日志 错误日志是 MySQL 中最重要的日志之一,它记录了当 mysql 启动 ...

  6. MySQL允许远程登录的授权方法

    泛授权方式 数据库本地直接登录上数据库: mysql -h localhost -u root 然后执行以下命令,授权完后直接就可以远程连接上.mysql>GRANT ALL PRIVILEGE ...

  7. 4.3 IAT Hook 挂钩技术

    IAT(Import Address Table)Hook是一种针对Windows操作系统的API Hooking 技术,用于修改应用程序对动态链接库(DLL)中导入函数的调用.IAT是一个数据结构, ...

  8. vue2原理初探-数据代理和数据劫持

    本篇文章主要想简单聊聊vue如何实现数据修改,页面联动的底层原理. 当然,篇幅有限,只是自己一些浅显的认知而已,我会从一下几个方面去聊,希望对你有所帮助. 几个基础知识点 数据代理 数据劫持 完整de ...

  9. A piece of cake

    1. A piece of cake(易事情)2. Break a leg(祝好运)3. Don't count your chickens before they hatch(不要过早乐观)4. D ...

  10. 【第一章 web入门】afr_3——模板注入与proc文件夹

    [第一章 web入门]afr_3--模板注入与proc文件夹 题目来源n1book,buu上的环境 看题 url中提供了name参数,类似在路径中进行了文件名查询然后展示: 随便输入一个数字: 说明肯 ...