HashMap 计算 Hash 值的扰动函数

2023-10-08 04:40

本文主要是介绍HashMap 计算 Hash 值的扰动函数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

微信公众号:运维开发故事,作者:老郑

计算过程


以下代码叫做 “扰动函数

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

理论上 hash 散列是一个 int 值,如果直接拿出来作为下标访问 hashmap 的话,考虑到二进制 32 位,取值范围在**-2147483648 ~ 2147483647**。大概有 40 亿个 key , 只要哈希函数映射比较均匀松散,一般很难出现碰撞。

一个客观的问题:要存下 40 亿长度的数组,服务器内存是不能放下的。通常咱们 HashMap 的默认长度为 16 。所以这个 hashCode , (key.hashCode ) 是不能直接来使用的。使用之前先做对数组长度的与运算,得到的值才能用来访问数组下标。

代码如下:

// n = hashmap 的长度p = tab[i = (n - 1) & hash])

这里为什么要使用 n -1 ,来进行与运算,这里详单与是一个”低位掩码”, 以默认长度 16 为例子。和某个数进行与运算,结果的大小是 < 16 的。如下所示:

    10000000 00100000 00001001&   00000000 00000000 00001111------------------------------    00000000 00000000 00001001  // 高位全部归 0, 只保留后四位

这个时候会有一个问题,如果本身的散列值分布松散,只要是取后面几位的话,碰撞也会非常严重。还有如果散列本身做得不好的话,分布上成等差数列的漏洞,可能出现最后几位出现规律性的重复。

这个时候“扰动函数”的价值就体现出来了。如下所示:

图片

在 hash 函数中有这样的一段代码:(h = key.hashCode()) ^ (h >>> 16) 右位移 16 位, 正好是32bit 的一半,与自己的高半区做成异或,就是为了**混合原始的哈希码的高位和低位,以此来加大低位的随机性。**并且混合后的低位掺杂了高位的部分特征,这样高位的信息变相保存下来。其实按照开发经验来说绝大多数情况使用的时候 HashMap 的长度不会超过 1000,所以提升低位的随机性可以提升可以减少 hash 冲突,提升程序性能。

如果感兴趣的小伙伴可以浏览下一下 Peter Lawlay 的专栏《An introduction to optimising a hashing strategy》的一个实验:他随机选取了 352 个字符串,在散列值完全没有冲突的前提下,对低位做掩码,取数组下标。

图片

结果显示, 当 hashmap 的数组长度为 512 的时候,也就是采用低位掩码取低 9 位的时候,在没有扰动函数的情况下,发生了 103 次碰撞,接近 30%。而在使用扰动函数之后只有 92 次碰撞。碰撞减少了将近10%。说明扰动函数确实有功效的。

但是明显 Java 8 觉得扰动做一次就够用了,做 4 次的话,可能边际效用也不大, 为了效率考虑就改成了一次。

代码演示‍


import java.lang.reflect.Field;import java.util.HashMap;/** * HashMap 计算 hashKey * <p> * 演示:扰动函数 * * @see HashMap#hash(Object) */public class HashKeyTest {    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {        HashMap<String, String> map = new HashMap<>();        String k = "王羲之";        String v = "大书法家";        map.put(k, v);        Field field = map.getClass().getDeclaredField("table");        field.setAccessible(Boolean.TRUE);        Object[] nodes = (Object[]) field.get(map);        int h = k.hashCode();        System.out.println("  h=" + h);        System.out.println();        // 调用 hashCode 结果        System.out.println("  h=hashCode()    " + num0x(h) + "  调用 hashCode");        // 无符号右移 16        System.out.println("  h>>>16          " + num0x(h >>> 16));        System.out.println("--------------------------------------------------");        // 计算 hash        System.out.println("  hash=h^(h>>>16) " + num0x(h ^ (h >>> 16)) + "  计算 hash");        System.out.println("--------------------------------------------------");        // 计算下标        System.out.println("  (n-1)&hash      " + num0x(15 & (h ^ (h >>> 16))) + "  计算下标");        System.out.println();        int idx = (15 & (h ^ (h >>> 16)));        // 输出下标        System.out.println("  下标: " + idx);        // 在下标中去获取数据        System.out.println("  查询结果:" + nodes[idx]);    }    /**     * 输入 int 转换为 二进制字符串     *     * @param num 数字     * @return 二进制字符串     */    static String num0x(int num) {        String num0x = "";        for (int i = 31; i >= 0; i--) {            num0x += (num & 1 << i) == 0 ? "0" : "1";        }        return num0x;    }}

运行结果如下:

图片

参考资料:https://www.zhihu.com/question/20733617

最后,求关注。如果你还想看更多优质原创文章,欢迎关注我们的公众号「运维开发故事」。


我是 老郑,《运维开发故事》公众号团队中的一员,某网 Java 程序员,这里不仅有硬核的技术干货,还有我们对技术的思考和感悟,欢迎关注我们的公众号,期待和你一起成长!

这篇关于HashMap 计算 Hash 值的扰动函数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/162759

相关文章

MySQL常用字符串函数示例和场景介绍

《MySQL常用字符串函数示例和场景介绍》MySQL提供了丰富的字符串函数帮助我们高效地对字符串进行处理、转换和分析,本文我将全面且深入地介绍MySQL常用的字符串函数,并结合具体示例和场景,帮你熟练... 目录一、字符串函数概述1.1 字符串函数的作用1.2 字符串函数分类二、字符串长度与统计函数2.1

python使用try函数详解

《python使用try函数详解》Pythontry语句用于异常处理,支持捕获特定/多种异常、else/final子句确保资源释放,结合with语句自动清理,可自定义异常及嵌套结构,灵活应对错误场景... 目录try 函数的基本语法捕获特定异常捕获多个异常使用 else 子句使用 finally 子句捕获所

postgresql使用UUID函数的方法

《postgresql使用UUID函数的方法》本文给大家介绍postgresql使用UUID函数的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录PostgreSQL有两种生成uuid的方法。可以先通过sql查看是否已安装扩展函数,和可以安装的扩展函数

MySQL字符串常用函数详解

《MySQL字符串常用函数详解》本文给大家介绍MySQL字符串常用函数,本文结合实例代码给大家介绍的非常详细,对大家学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql字符串常用函数一、获取二、大小写转换三、拼接四、截取五、比较、反转、替换六、去空白、填充MySQL字符串常用函数一、

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

MySql基本查询之表的增删查改+聚合函数案例详解

《MySql基本查询之表的增删查改+聚合函数案例详解》本文详解SQL的CURD操作INSERT用于数据插入(单行/多行及冲突处理),SELECT实现数据检索(列选择、条件过滤、排序分页),UPDATE... 目录一、Create1.1 单行数据 + 全列插入1.2 多行数据 + 指定列插入1.3 插入否则更

PostgreSQL中rank()窗口函数实用指南与示例

《PostgreSQL中rank()窗口函数实用指南与示例》在数据分析和数据库管理中,经常需要对数据进行排名操作,PostgreSQL提供了强大的窗口函数rank(),可以方便地对结果集中的行进行排名... 目录一、rank()函数简介二、基础示例:部门内员工薪资排名示例数据排名查询三、高级应用示例1. 每

全面掌握 SQL 中的 DATEDIFF函数及用法最佳实践

《全面掌握SQL中的DATEDIFF函数及用法最佳实践》本文解析DATEDIFF在不同数据库中的差异,强调其边界计算原理,探讨应用场景及陷阱,推荐根据需求选择TIMESTAMPDIFF或inte... 目录1. 核心概念:DATEDIFF 究竟在计算什么?2. 主流数据库中的 DATEDIFF 实现2.1

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串

MySQL 中的 CAST 函数详解及常见用法

《MySQL中的CAST函数详解及常见用法》CAST函数是MySQL中用于数据类型转换的重要函数,它允许你将一个值从一种数据类型转换为另一种数据类型,本文给大家介绍MySQL中的CAST... 目录mysql 中的 CAST 函数详解一、基本语法二、支持的数据类型三、常见用法示例1. 字符串转数字2. 数字