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. TypeScript:接口

    介绍 TypeScript的核心原则之一是对值所有的结构类型进行类型检查.在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义约束. 接口的基本使用 interface ...

  2. ValueError: Max value is 14 解决方案

    方案一(有时会失效): 将EXCEL文件中的格式全部清除即可.最好是复制,然后只粘贴值. 方案二(指定引擎): data = pd.read_excel(path, engine="open ...

  3. [nginx]编译安装openresty

    前言 OpenResty是一个基于Nginx和Lua的高性能Web平台,其内部集成了大量精良的Lua库.第三方模块以及大多数的依赖项.用于方便地搭建能够处理超高并发.扩展性极高的动态 Web 应用.W ...

  4. nginx反向代理常用基本配置

    http反向代理 http { ...     server {     listen 80;         location / {         proxy_pass http://192.1 ...

  5. CTFshow misc1-10

    小提示:需要从图片上提取flag文字,可以通过截图翻译或者微信发送图片,这两个的ai图像识别挺好用的. misc1: 解压打开就能看见flag,提取出来就行 misc2: 记事本打开,看见 ng字符, ...

  6. 白话领域驱动设计DDD

    容我找个借口先,日常工作太忙,写作略有荒废.一直想聊下领域驱动设计,以下简称DDD,之前也看过一些教程,公司今年两个项目--银行核心和信用卡核心,都深度运用DDD成功落地,有人说DDD挺难理解,在此讲 ...

  7. python实现图片提取文字功能

    安装需要的包 # pip install pytesseract # pip install Pillow # 安装OCR环境 # 下载exe安装文件 # https://digi.bib.uni-m ...

  8. [TSG开发日志](一)软件基础框架

    目录 前言 说明 框架 TSG_Framework 一.底层信号机制 TSG_Caller 二.参数类型声明 TSG_Params 三.设备类声明 TSG_Device 四.设备配置文件控制 TSG_ ...

  9. iframe子窗口调用父窗口方法

    //一个iframe页面调用另一个iframe页面的方法self.parent.frames["sort_bottom"].mapp($("#id").val( ...

  10. 【matplotlib基础】--绘图配置

    Matplotlib 提供了大量配置参数,这些参数可以但不限于让我们从整体上调整通过 Matplotlib 绘制的图形样式,这里面的参数还有很多是功能性的,和其他工具结合时需要用的配置. 通过plt. ...