HashMap之扰动函数

JDK8对扰动函数的修改,只进行了一次移位(又移16bit),再和key.hashCode()做异或,如图

1 //JDK8中的散列值优化函数
2     static final int hash(Object key){
3         int h;
4         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
5     }

HashMap扩容之前的数组初始大小才16,所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来访问数组下标。源码中模运算是在这个indexFor( )函数里完成的。

bucketIndex = indexFor(int h, table.length);

其中IndexFor代码

1 static int indexFor(int h, int length){
2     return h & (length - 1);
3 }

这也正好解释了为什么HashMap的数组长度要取2的整次幂。因为这样(数组长度-1)正好相当于一个“低位掩码”。“与”操作的结果就是散列值的高位全部归零,只保留低位值,用来做数组下标访问。以初始长度16为例,16-1=15。2进制表示是00000000 00000000 00001111。和某散列值做“与”操作如下,结果就是截取了最低的四位值。

但这时候问题就来了,这样就算我的散列值分布再松散,要是只取最后几位的话,碰撞也会很严重。更要命的是如果散列本身做得不好,分布上成等差数列的漏洞,恰好使最后几个低位呈现规律性重复,就无比蛋疼。

这时候“扰动函数”的价值就体现出来了,说到这里大家应该猜出来了。看下面这个图,

右位移16位,正好是32bit的一半,自己的高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。

JDK 7做了4次右移,估计是边际效应的原因,JDK8就只做了一次右移。

时间: 2024-10-29 10:45:42

HashMap之扰动函数的相关文章

关于HashMap中的扰动函数的疑问

最近再看jdk8的hashmap源码,当看到这一步的时候有点疑问,去网上搜了一下,看到的所有文章基本上都是一篇抄一篇的(反正目前各大社区就是这么个状况),那个意思就是让高16位也参与运算,增加结果的随机性,减小hash碰撞??? 乍一听好像是那么回事,但是越想越不对劲:我怎么都觉得是无论怎么运算最后不都是看低几位吗,在哪个固定的长度里每个数出现的概率不还是随机的吗(1/length):高位参与运算之后肯定能保证的是原来低位相同的值更加不同,但是不能保证本来不同的值运算后还是不通吧.怀着各种疑问,

从数组到HashMap之算法解释

一.数组是什么? 忘了在哪本书里曾看到过类似这样的一句话"所有的数据结构都是数组的演化",想想其实是有道理的,因为计算机的内存其实就是线性的存储空间. Java示例代码:int[] array = new int[5] JVM执行时会在堆中分配10个字节的内存空间,看起来就是这样的: 这样的数据结构可以很方便地通过数组下标存取数据,但在查找时需要遍历数组,平均时间复杂度为O(n/2). 当数据量很大或者查找操作频繁的时候,这样的遍历操作几乎是不可接受的.那么,如何才能够在更短的时间内快

【集合之HashMap】HashMap实现原理及非线程安全原因

要知道HashMap是什么,首先要搞清楚它的数据结构,在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外.HashMap实际上是一个数组和链表的结合体(在数据结构中,一般称之为“链表散列“). 从图中我们可以看到一个HashMap就是一个数组结构,当新建一个HashMap的时候,就会初始化一个数组. HashMap源码中的3个常量, 1 /** 2 * The default initial c

学习HashMap的笔记

对于HashMap只是学习了下put,remove方法,hashMap是数组+链表+红黑树组成 所以下面贴出我自己给代码的注释,看不懂的见谅哈,毕竟我也是刚了解,如果有错误的地方请指出,非常感谢 put方法(图片和代码一起吧,屏幕小的时候 看图片合适点,看图片的话建议下载下来看,比较清晰): final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node&

面试必备:HashMap源码解析(JDK8)

1 概述 本文将从几个常用方法下手,来阅读HashMap的源码. 按照从构造方法->常用API(增.删.改.查)的顺序来阅读源码,并会讲解阅读方法中涉及的一些变量的意义.了解HashMap的特点.适用场景. 如果本文中有不正确的结论.说法,请大家提出和我讨论,共同进步,谢谢. 2 概要 概括的说,HashMap 是一个关联数组.哈希表,它是线程不安全的,允许key为null,value为null.遍历时无序. 其底层数据结构是数组称之为哈希桶,每个桶里面放的是链表,链表中的每个节点,就是哈希表中

HashMap是如何工作的

1 HashMap在JAVA中的怎么工作的? 基于Hash的原理 2 什么是哈希? 最简单形式的 hash,是一种在对任何变量/对象的属性应用任何公式/算法后, 为其分配唯一代码的方法. 一个真正的hash方法必须遵循下面的原则 哈希函数每次在相同或相等的对象上应用哈希函数时, 应每次返回相同的哈希码.换句话说, 两个相等的对象必须一致地生成相同的哈希码. Java 中所有的对象都有 Hash 方法 Java中的所有对象都继承 Object 类中定义的 hashCode() 函数的默认实现. 此

2、JDK8中的HashMap实现原理及源码分析

本篇提纲.png 本篇所述源码基于JDK1.8.0_121 在写上一篇线性表的文章的时候,笔者看的是Android源码中support24中的Java代码,当时发现这个ArrayList和LinkedList的源码和Java官方的没有什么区别,然而在阅读HashMap源码的时候,却发现Android中的Java与官方版的出入略大,遂不得不转而用Eclipse导入jdk源码阅读,这里不得不吐槽一句,用惯了IDEA的快捷键,Eclispe还真是用不习惯~~好了,接下来我们言归正传: 一.什么是Has

HashMap 简介

HashMap 简介 HashMap 主要用来存放键值对,它基于哈希表的Map接口实现,是常用的Java集合之一. JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间. 底层数据结构分析 JDK1.8之前 JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也

Android之HashMap讲解

前言 HashMap是面试中比较常见的问题,这一篇,我们将通过阅读源码,了解其设计原理以及以下问题 HashMap的实现原理 初始容量为什么是2的倍数 如何resize 是否线程安全 常用参数 //最大容量 2的30次方 static final int MAXIMUM_CAPACITY = 1 << 30; //初始容量为16 扩容时才会触发 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 //默认的加载