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. Django: Token分发

    Django后台token分发 在settings.py中引入 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'd ...

  2. 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 ...

  3. docker网络 bridge 与overlay 模式

    转载请注明出处: 1.bridge网络模式 工作原理:  在Bridge模式中,Docker通过创建一个虚拟网络桥接器(bridge)将容器连接到主机上的物理网络接口.每个容器都会被分配一个IP地址, ...

  4. 形象谈JVM-第一章-认识JVM

    对jvm的历史不做过多介绍,感兴趣的同学可以去自行搜索. 我们直接以HotSpot VM(Virtual Machine)举例. why  为什么要有虚拟机? 举一个形象的例子:手机现在几乎是人手一台 ...

  5. Unity UGUI的Slider(滑动条)件组的介绍及使用

    Unity UGUI的Slider(滑动条)件组的介绍及使用 1. 什么是Slider组件? Slider(滑动条)是Unity UGUI中的一种常用UI组件用,于在用户界面中实现滑动选择的功能.通过 ...

  6. CodeForces 1408D Searchlights

    题意 在二维平面有\(n\)个海盗,\(m\)个探照灯,你有两种操作 将所有海盗往上走一步 将所有海盗往右走一步 设海盗为\((a_i,b_i)\),探照灯为\((c_j,d_j)\),当且仅当\(a ...

  7. 性能调优 session 1 - 计算机体系结构 量化研究方法

    近期本人参与的存储系统项目进入到性能调优阶段,当前系统的性能指标离项目预期目标还有较大差距.本人一直奉行"理论指导下的实践",尤其在调试初期,更要抓住主要矛盾,投入最少的资源来获取 ...

  8. 2016A

    #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm& ...

  9. 「luogu - P3911」最小公倍数之和

    link. Denote \(cnt_{x}\) = the number of occurrences of \(x\), \(h\) = the maximum of \(a_i\), there ...

  10. 【matplotlib 实战】--堆叠面积图

    堆叠面积图和面积图都是用于展示数据随时间变化趋势的统计图表,但它们的特点有所不同.面积图的特点在于它能够直观地展示数量之间的关系,而且不需要标注数据点,可以轻松地观察数据的变化趋势.而堆叠面积图则更适 ...