为什么Java String哈希乘数为31?

【Java】String hashCode 这个数字 31 1. 背景 原文:String hashCode 这个数字,很多人不知道! 生动有趣的文章:哈希表哪家强?几大编程语言吵起来了! 好文章:面试28k职位,老乡面试官从HashCode到HashMap给我讲了一下午!「回家赶忙整理出1.6万字的面试材料」 建议直接看这个 某天,我在写代码的时候,无意中点开了 String hashCode 方法。然后大致看了一下 hashCode 的实现,发现并不是很复杂。但是我从源码中发现了一个奇怪的数字,也就是本文的主角31。 这个数字居然不是用常量声明的,所以没法从. 阅读详情

前面简单介绍了[ 经典的Times 33 哈希算法 ],这篇我们通过分析Java 1.8 String类的哈希算法,继续聊聊对乘数的选择。

String类的hashCode()源码

/**Cachethe hash codeforthestring*/privateinthash;/**Returnsa hash codeforthisstring.Thehash codeforaStringobjectis computedass[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]usingintarithmetic, where s[i] is the ith characterofthestring, n is the lengthofthestring,and^ indicates exponentiation. (Thehashvalueofthe emptystringis zero.) */publicinthashCode(){inth = hash;if(h ==0&&value.length >0) {charval[]=value;for(inti =0; i

可以看到,String的哈希算法也是采用了Times 33的思路,只不过乘数选择了31。

其中

hash默认值为0.

判断h == 0是为了缓存哈希值.

判断value.length > 0是因为空字符串的哈希值为0.

用数据说话

前一篇我们提到:

这个神奇的数字33,为什么用来计算哈希的效果会比其他许多常数(无论是否为质数)更有效,并没有人给过足够充分的解释。因此,Ralf S. Engelschall尝试通过自己的方法解释其原因。通过对1到256中的每个数字进行测试,发现偶数的哈希效果非常差,根据用不了。而剩下的128个奇数,除了1之外,效果都差不多。这些奇数在分布上都表现不错,对哈希表的填充覆盖大概在86%。

从哈希效果来看(Chi^2应该是指卡方分布),虽然33并不一定是最好的数值。但17、31、33、63、127和129等相对其他的奇数的一个很明显的优势是,由于这些奇数与16、32、64、128只相差1,可以通过移位(如1 << 4 = 16)和加减1来代替乘法,速度更快。

那么接下来,我们通过实验数据,来看看偶数、奇数,以及17、31、33、63、127和129等这些神奇数字的哈希效果,来验证Ralf S. Engelschall的说法。

环境准备

个人笔记本,Windows 7操作系统,酷睿i5双核64位CPU。

测试数据:CentOS Linux release 7.5.1804的/usr/share/dict/words字典文件对应的所有单词。

由于CentOS上找不到该字典文件,通过yum -y install words进行了安装。

/usr/share/dict/words共有479828个单词,该文件链接的原始文件为linux.words。

计算冲突率与哈希耗时

测试代码

/**

* 以1-256为乘数,分别计算/usr/share/dict/words所有单词的哈希冲突率、总耗时.

*

* @throws IOException

*/@TestpublicvoidtestHash()throwsIOException {List words = getWords();System.out.println();System.out.println("multiplier, conflictSize, conflictRate, timeCost, listSize, minHash, maxHash");for(inti =1; i <=256; i++) {computeConflictRate(words, i);}}/**

* 读取/usr/share/dict/words所有单词

*

* @return

* @throws IOException

*/privateList getWords()throwsIOException {// read fileInputStream is = HashConflictTester.class.getClassLoader().getResourceAsStream("linux.words");List lines = IOUtils.readLines(is,"UTF-8");returnlines;}/**

* 计算冲突率

*

* @param lines

*/privatevoidcomputeConflictRate(List lines,intmultiplier) {// compute hashlongstartTime = System.currentTimeMillis();List hashList = computeHashes(lines, multiplier);longtimeCost = System.currentTimeMillis() - startTime;// find max and min hashComparator comparator = (x,y) -> x > y ?1: (x < y ?-1:0);intmaxHash = hashList.parallelStream().max(comparator).get();intminHash = hashList.parallelStream().min(comparator).get();// hash setSet hashSet = hashList.parallelStream().collect(Collectors.toSet());intconflictSize = lines.size() - hashSet.size();floatconflictRate = conflictSize *1.0f / lines.size();System.out.println(String.format("%s, %s, %s, %s, %s, %s, %s", multiplier, conflictSize, conflictRate, timeCost, lines.size(), minHash, maxHash));}/**

* 根据乘数计算hash值

*

* @param lines

* @param multiplier

* @return

*/privateList computeHashes(List lines,intmultiplier) {Function hashFunction = x -> {inthash =0;for(inti =0; i < x.length(); i++) {hash = (multiplier * hash) + x.charAt(i);}returnhash;};returnlines.parallelStream().map(hashFunction).collect(Collectors.toList());}复制代码

执行测试方法testHash(),稍等片刻后,我们将得到一份测试报告。

哈希冲突率降序排序

通过对哈希冲突率进行降序排序,得到下面的结果。

结果分析

偶数的冲突率基本都很高,只有少数例外。

较小的乘数,冲突率也比较高,如1至20。

乘数1、2、256的分布不均匀。Java哈希值为32位int类型,取值范围为[-2147483648,2147483647]。

13467292-322208dbf5a3716d

哈希耗时降序排序

我们再对冲突数量为1000以内的乘数进行分析,通过对执行耗时进行降序排序,得到下面的结果。

分析17、31、33、63、127和129

17在上一轮已经出局。

63执行计算耗时比较长。

31、33的冲突率分别为0.13%、0.14%,执行耗时分别为10、11,实时基本相当。

127、129的冲突率分别为0.01%、0.004%,执行耗时分别为9、10。

总体上看,129执行耗时低,冲突率也是最小的,似乎先择它更为合适?

13467292-fedea23264e93675

哈希分布情况

将整个哈希空间[-2147483648,2147483647]分为128个分区,分别统计每个分区的哈希值数量,以此来观察各个乘数的分布情况。每个分区的哈希桶位为2^32 / 128 = 33554432。

之所以通过分区来统计,主要是因为单词数太多,尝试过画成图表后密密麻麻的,无法直观的观察对比。

现在加群即可获取Java工程化、高性能及分布式、高性能、高架构。性能调 优、Spring,MyBatis,Netty源码分析和大数据等多个知识点高级进阶干货 的直播免费学习权限及领取相关资料,群号:835638062 点击链接加入群聊 【Java高级架构】:https://jq.qq.com/?_wv=1027&k=5S3kL3v

计算哈希分布代码

@TestpublicvoidtestHashDistribution()throwsIOException {int[] multipliers = {2,17,31,33,63,127,73,133,237,161};List words = getWords();for(intmultiplier : multipliers) {List hashList = computeHashes(words, multiplier);Map hashMap = partition(hashList);System.out.println("\n"+ multiplier +"\n,count");hashMap.forEach((x, y) -> System.out.println(x +","+ y));}}/**

* 将整个哈希空间等分成128份,统计每个空间内的哈希值数量

*

* @param hashs

*/publicstaticMap partition(List hashs) {// step = 2^32 / 128 = 33554432finalintstep =33554432;List nums =newArrayList<>();Map statistics =newLinkedHashMap<>();intstart =0;for(longi = Integer.MIN_VALUE; i <= Integer.MAX_VALUE; i += step) {finallongmin= i;finallongmax=min+ step;intnum = (int) hashs.parallelStream().filter(x -> x >=min&& x x + y).get();asserthashNum == hashs.size();returnstatistics;}复制代码

生成数据之后,保存文本为csv后缀,通过Excel打开。再通过Excel的图表功能,选择柱状图,生成以下图表。

乘数2

13467292-20c81c54e0c34295

乘数17

13467292-ef3f7bdd94164ac4

乘数31

13467292-00a73391fa410164

乘数33

13467292-f24e0e184610cc9c

乘数73

13467292-adfa9cfab1f0cb02

乘数127

13467292-8134ebc958737e4e

乘数133

13467292-1c12c0c6f7a3f50d

乘数161

13467292-ec7422d176a02489

乘数237

13467292-e6cbbeb91a086608

除了2和17,其他数字的分布基本都比较均匀。

总结

现在我们基本了解了Java String类的哈希乘数选择31的原因了,主要有以下几点。

31是奇素数。

哈希分布较为均匀。偶数的冲突率基本都很高,只有少数例外。较小的乘数,冲突率也比较高,如1至20。

哈希计算速度快。可用移位和减法来代替乘法。现代的VM可以自动完成这种优化,如31 * i = (i << 5) - i。

31和33的计算速度和哈希分布基本一致,整体表现好,选择它们就很自然了。

当参与哈希计算的项有很多个时,越大的乘数就越有可能出现结果溢出,从而丢失信息。我想这也是原因之一吧。

尽管从测试结果来看,比31、33大的奇数整体表现有更好的选择。然而31、33不仅整体表现好,而且32的移位操作是最少的,理论上来讲计算速度应该是最快的。

最后说明一下,我通过另外两台Linux服务器进行测试对比,发现结果基本一致。但以上测试方法不是很严谨,与实际生产运行可能存在偏差,结果仅供参考。

几个常用实现选项

13467292-220e5cd78a0fbd74

其中

INITIAL_VALUE:哈希初始值。Java String的初始值hash=0。

a:哈希乘数。Java String的哈希乘数为31。

参考

stackoverflow.com/questions/2…

segmentfault.com/a/119000001…

en.wikipedia.org/wiki/Univer…

《Effective Java中文版本》第2版

现在加群即可获取Java工程化、高性能及分布式、高性能、高架构。性能调 优、Spring,MyBatis,Netty源码分析和大数据等多个知识点高级进阶干货 的直播免费学习权限及领取相关资料,群号:835638062 点击链接加入群聊 【Java高级架构】:https://jq.qq.com/?_wv=1027&k=5S3kL3v

matlab 三维绘图 抛光,瓷砖抛光过程建模与仿真 :瓷砖抛 光过程 建模与仿真朱 成 就(广 东工业大学 机 电工程 学院, 广东广州 )摘要 :瓷砖的抛磨影响 因素众 多 ,实际磨削加工时容易出现漏抛 、返抛现象 。对陶瓷抛光机磨头进行运动学分析 ,并基于不 同粒度号 的磨 粒在一定压力下的加工深度模型 ,探讨抛光机磨 头转 速 、横梁摆动频率 、传送速度对瓷砖抛光加工表 面均匀性 的影响,然 后结合 磨削机理使 用 对陶瓷抛光过程进行仿真 ... 阅读详情

相关推荐

避坑指南:QT Creator报Unknown module(s) in QT: serialport的5种解决方法

本文详细解析了Qt Creator报'Unknown module(s) in QT: serialport'错误的5种解决方法,包括检查Qt版本兼容性、安装缺失组件、配置Kit环境等。针对Windows和Linux平台提供具体操作步骤,帮助开发者快速解决串口通信模块问题,提升开发效率。

weixin_29197051的博客 157

哈希之详细介绍

(1)散列表(哈希表)的定义 目录(?)[+] 一般的查找 给你一个顺序表,你会如何查找某个给定的元素? 一般思路就是从表头开始,一个挨一个的比较记录a[i]与key的值是“=”还是“≠”,直到有相等才算是査找成功,返回i的值。 到了有序表(已排序的表)査找时,我们可以利用a[i]与key的“”,来折半査找,直到相等时査找成功返回i。

业精于勤,荒于嬉;行成于思,毁于随。 1477

LangChain开发避坑指南:为什么Pylance总提示‘无法解析导入‘?实测7种修复方案

本文深入解析了在VS Code中使用LangChain开发时,Pylance频繁提示‘无法解析导入’的根本原因——Python解释器与语言服务器的环境路径不一致。文章提供了从基础排查到深度配置的7种实测修复方案,包括切换解释器、配置extraPaths、使用pyrightconfig.json等,旨在帮助开发者彻底解决此问题,提升AI应用开发效率。

weixin_28826521的博客 447

哈希算法之 hashCode 为什么选择数字31

理由由2点: 1、质数的特性(1和本身),可以降低哈希算法的冲突率 { 验证:string 和 hashmap 举例 哈希值1=质数x随机数 哈希值2=随机数x随机数 31:[1、31] 32 [1、2、4、8、16、32] key = 哈希值 % 随机数 出现 0 的次数 31 2次 32 6次 } 2、计算方式优化 31=1>>5-1 乘法==位移运算+减法运算 ...

qq_35316639的博客 694

ARM64麒麟V10系统下PyTorch离线安装全攻略(附资源下载)

本文详细介绍了在ARM64架构的银河麒麟V10操作系统上,如何通过离线方式成功安装PyTorch深度学习框架。文章提供了从系统环境准备、依赖梳理、离线资源获取与验证,到分步安装、避坑指南及安装后验证的完整流程,并附有资源下载建议,旨在帮助开发者在国产化环境中高效部署AI开发平台。

metal的博客 746

告别Arduino IDE!用VSCode搭建Arduino UNO R3开发环境(含代码补全配置)

本文详细指导如何用VSCode替代Arduino IDE搭建Arduino UNO R3开发环境,包含代码补全配置和高效开发技巧。通过安装必要插件、配置项目结构和优化工作流,可显著提升开发效率,特别适合复杂项目开发。文章还提供了常见问题解决方案和高级功能调优方法。

weixin_30698297的博客 478

【Java总结】详细探讨HashCode算法为什么采用31作为乘数

前两天写了几篇关于Hash函数和HashMap集合的文章,其中有个问题说的有点含糊 就是关于hashCode的计算为什么采用31这个数作为乘数的问题。 当时强硬的理解为31 这个数值原因为: 首先hash函数必须要选用质数(质数定义为在大于1的自然数中,除了1和它本身以外不再有其他因数又称为素数),这个是被科学家论证过的hash函数减少冲突的一个理论。 如果设置为偶数的话会存在溢出的情况,导...

zhujohnle的专栏 1万+

【音视频】PJSIP库——示例简介、C++类说明

aectest音频回声消除测试工具,用于演示音频处理模块中的回声消除(AEC)功能,通常用于 VoIP 场景中优化语音质量。auddemo音频播放 / 录制演示,展示如何使用 PJProject 的音频接口进行基本的音频数据采集、播放和处理,例如波形显示或格式转换。stereotest立体声处理测试,可能用于演示多声道音频的处理方式,如混音、声道分离等。tonegen音频信号生成工具,用于生成测试用的音频信号(如正弦波、音调等),常用于音频设备测试或调试。recfile。

郭老二 434

java中重写的hascode中的31是怎么来的

@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; }其中h

fenuang的博客 1119

GD32F303标准库开发:Keil5新建GD32标准库工程

GD32F303标准库开发:Keil5新建GD32标准库工程

MQ0522的博客 8902

11、Windows Autopilot 部署与设备注册全攻略

本文详细介绍了Windows Autopilot的完整部署流程,涵盖注册配置文件设置、注册状态页面(ESP)配置、设备注册方法(包括CSV导入、JSON注入、在线注册等)、自动化脚本实现及故障排除技巧。通过手动与PowerShell自动化两种方式指导用户完成配置,并提供流程图、最佳实践和常见问题解答,帮助IT管理员高效实现设备零接触部署与管理。

ii567的博客 342

为什么Java String哈希函数乘数为31?

今天给大家介绍一个底层数字问题——为什么Java String哈希函数乘数为31?

m0_59236433的博客 334

PyCharm 怎么不升级直接使用Jupyter Notebook

在PyCharm 中需要升级到 PyCharm Professional 版本,才能直接使用 Jupyter Notebook。从截图来看,Jupyter Notebook 已经成功启动,并且正在运行。这将在默认网络浏览器中打开一个新的标签页,显示文件浏览器界面。可以在这里创建和编辑。文件添加到 PyCharm 项目中,并使用纯文本编辑功能进行修改。支持与外部工具集成,可以配置它以调用命令行工具或脚本来启动。,参数可以根据需要设置,工作目录设置为项目根目录。,你仍然可以在本地环境中安装和运行。

Davina_yu的博客 996

String hashCode 这个数字,很多人不知道!

作者:coolblog segmentfault.com/a/1190000010799123 2019-09-24 09:36:00 1. 背景 某天,我在写代码的时候,无意中点开了 String hashCode 方法。然后大致看了一下 hashCode 的实现,发现并不是很复杂。但是我从源码中发现了一个奇怪的数字,也就是本文的主角31。 这个数字居然不是用常量声明的,所以没法从字面意思上推断这个数字的用途。后来带着疑问和好奇心,到网上去找资料查询一下。在看完资料后,默默的感叹了一句,原来是这样啊。.

Java技术栈,分享最主流的Java技术 528

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

String-HashCode源码分析

特性说明算法h = 31 * h + char[i] //char[i]即string的一个ascii码乘数31(质数,可优化为位操作,碰撞率低)缓存计算结果存储在hash字段中,利用String的不可变性实现高效缓存目的为字符串生成一个分布良好的、唯一的int值标识,用于基于哈希的集合类(如HashMap, HashSet)哈希碰撞hashcode范围是int,根据鸽巢原理原理来说有限的空间上无数个不同字符串肯定会重复,重复即会发生hashcode相同即哈希碰撞。

weixin_37813118的博客 1366

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

面试栽了:String.hashCode()返回值超出 int 最大取值范围会怎样?

我们知道String.hashCode()返回的是一个int类型的数值,那当某个字符串的hash值超出int的最大范围后会发生什么呢?首先我们来看下String.hashCode()的源码,看看它是如何计算一个字符串的hash值: 从源码中我们可以看出:1.String有一个私有变量hash来缓存哈希值,即当该串第一次调用hashCode()方法时,hash默认值为0,继续执行,当字符串长度大于0时计算出一个哈希值赋给hash,之后再调用hashCode()方法时不会重新计算,直接返回hash2.计算时

m0_71777195的博客 330

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

为什么String中的Java hashCode()使用31作为乘数?

Per the Java documentation, the hash code for a String object is computed as: 根据Java文档, String对象的哈希

asdfgh0077的博客 454

关于String类中的hashcode的相关整理

为什么JavaString哈希乘数选择31? 31是奇素数(可以充分利用原始数据包含的信息) 哈希分布比较均匀。偶数的冲突率很高,只有少数例外。小乘数(1-20)的冲突率也很高 哈希计算很快。选用31是JVM的一种优化。位移和减法操作比乘法操作快速 31*hash=(hash<<5)-hash; 31和33的计算速度和哈希分布基本一致,整体表现好(大于31,33的奇...

weixin_39345957的博客 362

当String.hashCode方法返回的hash值超出int的最大取值范围后会发生什么?

我们知道String.hashCode()返回的是一个int类型的数值,那当某个字符串的hash值超出int的最大范围后会发生什么呢? 首先我们来看下String.hashCode()的源码,看看它是如何计算一个字符串的hash值: /** The value is used for character storage. */ private final char value[]; ...

weixin_45661382的博客 4641

面经手册 · 第2篇《数据结构,HashCode为什么使用31作为乘数?》

作者:小傅哥 博客:https://bugstack.cn 沉淀、分享、成长,让自己和他人都能有所收获!???? 一、前言 在面经手册的前两篇介绍了《面试官都问我啥》和《认知自己的技术栈盲区》,这两篇内容主要为了说明面试过程的考查范围,包括个人的自我介绍、技术栈积累、项目经验等,以及在技术栈盲区篇章中介绍了一个整套技术栈在系统架构用的应用,以此全方面的扫描自己有哪些盲区还需要补充。而接下来的章节会以各个系列的技术栈中遇到的面试题作为切入点,讲解技术要点,了解技术原理,包括;数据结构、数据算法、技术栈、框

bugstack虫洞栈 2212

细读源码之Java String(二)

Java中的String类绝对是使用最广泛的类,本文就带着大家细读一下JDK8下的String源码(接上篇)

Mssyaa的博客 486

对HashCode的理解

文章目录Hash什么是hash常见Hash函数直接取余法乘数去整法平方取中法HashCodeObject.hashCode()Objects.hashCode(),Arrays.hashCode()String.hashCode()Interger.hashCode()hashCode的作用equals和hashCode为什么是31 Hash 什么是hash hash,翻译散列或哈希,就是把任意长...

N_a_n的博客 925
上一篇: 程序员能纯靠技术渡过中年危机吗?
下一篇: Java架构视频教程,让你从入门到精通!
普通网友
博客等级 码龄8年 55粉丝 197原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值