HashMap与HashTable的哈希算法——JDK1.9源码阅读总结

下面是HashTable源码中的put方法:

注意上面注释标注的地方:

HashTable对于元素在哈希表中的坐标算法是:

  1. 将对象自身的哈希值key.hashCode()变为正数:hash & 0x7FFFFFFF
  2. 将上面得到的哈希值对表长取余,映射到哈希表中去。

HashMap中哈希算法比HashTable中的稍微复杂一点。总体可以分为两步:

一、重新计算key本身的哈希值

上面代码中,首先是一个三目运算符,判断key是不是等于null,等于null,则返回0作为哈希值。否则,运算(h=key.hashCode()) ^ (h >>> 16),将key的哈希值的高位与低位异或的结果作为低位,改为不变。

‘>>>’是无符号右移操作,高位补0.

为什么要这么做呢?下面这一点讲了过后我们就明白了

二、哈希坐标的计算

同样以put方法为例

从最后一行我们可以看出,HashMap的哈希坐标计算方法是: (n - 1) & hash,其中hash就是我们第一点讲的改进哈希码。

HashMap为什么要使用改进的hash码?

举例分析如下,假设key的原始哈希值是’1111 1111 1111 1111 1111 0000 1110 1010’


(ps:图片来自:https://blog.csdn.net/john_520/article/details/57415084)

我们注意到,在上面这种哈希表长度较小的情况下,哈希码只有低4位与表的长度进行了关联性计算。这会造成哈希码的不充分使用,从而更容易引起哈希冲突。为了充分利用哈希码的高位,HashMap通过(h=key.hashCode())
^ (h >>> 16)运算,将高位与低位异或,使得即使在表长较小的情况下,高位也能参与计算,使得冲突的概率减小了。

HashMap为什么要使用 (n - 1)
& hash ,而不是HashTable中求模的方式来计算哈希坐标呢?

我在Stack Overflow上找到了一个解答:

意思是说:“规范的解决方法是将哈希值与表长取模,而这个方式((n - 1) & hash
武汉英语学校充分利用了HashMap的表长是2的整数次幂的事实,使用效率较高的位与运算(取模的高度优化)来替代昂贵的取模运算。”

实际上这两种方式的实质是一样的,它只是利用了这样一个事实:在n是2的幂的情况下,(n - 1) & hash 等同于 hash%h。

在n是2的幂的情况下,为什么 (n - 1) & hash等同于
hash%h?

我们以10为例,演示如下:

可以看到,两种方式的计算结果是相同的(这不是巧合),这实际上是一个数学规律。

HashMap是怎么使得表长始终为2的整数次幂的?

在源码中有这样一个方法:

这个方法的作用如注释所说,是求大于cap的最小2的整数次幂。在用户指定的初始容量不是2的幂时,HashMap会调用该方法将其变得符合要求。此后,每次扩容时是这样的:

直接使用oldCap<<1来将容量扩大为原来的2倍,即乘以21

  1. 对数学规律的恰当应用可以优化代码的运行效率
  2. 位运算在JDK中运用的十分广泛,如上面讲解的使用位“与”运算替代求模。这种替代的内在原因是位运算比数学运算快很多。优化都在细节处。

我对JDK源码的阅读和中文注释都已经同步到Github,欢迎英语阅读困难户前往查看:)
链接是:https://github.com/Dodozhou/JDK,喜欢的话别忘了star哦。

原文地址:https://www.cnblogs.com/zhangyiqinga/p/9753642.html

时间: 2024-08-02 20:33:57

HashMap与HashTable的哈希算法——JDK1.9源码阅读总结的相关文章

JDK1.8源码阅读-java.lang.Object.java

1.getClass() public final native Class<?> getClass(); 获得的是变量实际的类型,比如 得到的输出结果是 hashmap. getClass 获得的实际类型是Class<? extends |X|> 其中X是变量声明时的类型 2.hashCode() 一个对象中无论被调用多少次,都会返回同一个值.以保证不会影响equals()的比较结果.但是在两次不同的运行中,不能保证相同的hashCode.(挺好理解的,就是在一次) 3.equa

【JUC】JDK1.8源码分析之ConcurrentHashMap(一)

一.前言 最近几天忙着做点别的东西,今天终于有时间分析源码了,看源码感觉很爽,并且发现ConcurrentHashMap在JDK1.8版本与之前的版本在并发控制上存在很大的差别,很有必要进行认真的分析,下面进行源码分析. 二.ConcurrentHashMap数据结构 之前已经提及过,ConcurrentHashMap相比HashMap而言,是多线程安全的,其底层数据与HashMap的数据结构相同,数据结构如下 说明:ConcurrentHashMap的数据结构(数组+链表+红黑树),桶中的结构

【JUC】JDK1.8源码分析之ConcurrentHashMap

一.前言 最近几天忙着做点别的东西,今天终于有时间分析源码了,看源码感觉很爽,并且发现ConcurrentHashMap在JDK1.8版本与之前的版本在并发控制上存在很大的差别,很有必要进行认真的分析,下面进行源码分析. 二.ConcurrentHashMap数据结构 之前已经提及过,ConcurrentHashMap相比HashMap而言,是多线程安全的,其底层数据与HashMap的数据结构相同,数据结构如下 说明:ConcurrentHashMap的数据结构(数组+链表+红黑树),桶中的结构

Java Jdk1.8 HashMap源码阅读笔记一

最近在工作用到Map等一系列的集合,于是,想仔细看一下其具体实现. 一.结构 public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable 1.抽象类AbstractMap public abstract class AbstractMap<K,V> implements Map<K,V> 该类实现了Map接口,具体结

Java Jdk1.8 HashMap源码阅读笔记二

三.源码阅读 3.元素包含containsKey(Object key) /** * Returns <tt>true</tt> if this map contains a mapping for the * specified key. * * @param key The key whose presence in this map is to be tested * @return <tt>true</tt> if this map contains

JDK1.8源码学习之 HashMap.java

///JDK1.8源码学习之HashMap.java package java.util; import java.io.IOException; import java.io.InvalidObjectException; import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.function.BiConsu

JDK1.8源码学习-Object

JDK1.8源码学习-Object 目录 一.方法简介 1.一个本地方法,主要作用是将本地方法注册到虚拟机中. private static native void registerNatives(); static { registerNatives(); } 2.获取类的字节码对象 public final native Class<?> getClass(); 3.返回当前对象的hash值 public native int hashCode(); 4.比较党当前对象的引用是否和要比较的

spark.mllib源码阅读-分类算法4-DecisionTree

本篇博文主要围绕Spark上的决策树来讲解,我将分为2部分来阐述这一块的知识.第一部分会介绍一些决策树的基本概念.Spark下决策树的表示与存储.结点分类信息的存储.结点的特征选择与分类:第二部分通过一个Spark自带的示例来看看Spark的决策树的训练算法.另外,将本篇与上一篇博文"spark.mllib源码阅读bagging方法"的bagging子样本集抽样方法结合,也就理解了Spark下的决策森林树的实现过程. 第一部分: 决策树模型 分类决策树模型是一种描述对实例进行分类的树形

jdk源码阅读-HashMap

前置阅读: jdk源码阅读-Map : http://www.cnblogs.com/ccode/p/4645683.html 在前置阅读的文章里,已经提到HashMap是基于Hash表实现的,所以在讲解HashMap之前 ,有必要提前了解下Hash的原理. 参考<算法导论><算法>