当前位置: 首页 > news >正文

String-HashCode源码分析

霍纳法则

        在看源码前了解下霍纳法则:霍纳法则(Horner's Rule)是一种用于高效计算多项式的算法,通过减少乘法次数来提升计算效率。该法则将多项式从嵌套形式展开,适用于计算机科学和数值分析领域。  

算法原理

        给定一个多项式: [ P(x) = a_nx^n + a_{n-1}x^{n-1} + \cdots + a_1x + a_0 ] 霍纳法则将其重写为嵌套形式: [ P(x) = a_0 + x(a_1 + x(a_2 + \cdots + x(a_{n-1} + xa_n)\cdots)) ]

String的hashCode()方法使用了一个名为“多项式散列”或“霍纳法则”的算法。它并不是简单地计算字符串的ASCII和,而是通过一个公式 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 来生成一个int类型的哈希值,并且这个值会被缓存起来以避免重复计算。


源码分析

public final class String {/** Cache the hash code for the string */private int hash; // 默认值为 0public int hashCode() {int h = hash; // 1. 读取缓存的哈希值if (h == 0 && value.length > 0) { // 2. 如果缓存为0(且字符串非空),才进行计算char val[] = value; // 内部的字符数组for (int i = 0; i < value.length; i++) {h = 31 * h + val[i]; // 3. 核心计算:霍纳法则}hash = h; // 4. 计算完成后,将结果存入缓存字段}return h; // 5. 返回计算好或已缓存的哈希值}
}

实现原理分解

1. 缓存机制 (Caching)
  • String类有一个私有字段 private int hash;,专门用于缓存计算好的哈希值。

  • 首次调用hashCode()方法时,因为hash的初始值是0,所以会进入计算逻辑。

  • 计算完成后,将结果赋值给hash字段。

  • 之后再次调用hashCode()方法时,直接返回缓存的hash值,避免了重复计算,极大地提升了性能。因为String对象是不可变的,其内容一旦创建就不会改变,所以哈希值也永远不会变,缓存是绝对安全的。

2. 核心算法 (Polynomial Hashing)

算法的数学表达式是:
h(s) = s[0] * 31^(n-1) + s[1] * 31^(n-2) + ... + s[n-2] * 31^1 + s[n-1] * 31^0

在代码中,这个计算通过循环和乘加运算高效完成,应用了霍纳法则(Horner‘s method)进行优化,避免了直接计算高次幂。

h = 31 * h + val[i]

我们以一个简单的字符串 “Hi” 为例,手动计算一下:

  • 初始值 h = 0

  • 第一个字符 ‘H’ (ASCII 值为 72):

    • h = 31 * 0 + 72 -> h = 72

  • 第二个字符 ‘i’ (ASCII 值为 105):

    • h = 31 * 72 + 105

    • = 2232 + 105

    • = 2337

  • 所以字符串 “Hi” 的 hashCode 是 2337

你可以通过以下代码验证:

System.out.println("Hi".hashCode()); // 输出 2337
3. 为什么选择数字31?

这是一个精心选择的值,主要有以下几个原因:

  1. 质数 (Prime Number):31是一个质数。使用质数作为乘数可以帮助减少哈希碰撞(不同字符串产生相同哈希值的概率)。如果使用合数,比如偶数,那么乘以它相当于位移操作,信息更容易丢失。

  2. 性能优化 (Performance)31 * i 可以被JVM优化为 (i << 5) - i。这是一个非常高效的位运算,因为移位和减法操作远比乘法操作快。现代JVM可能已经具备自动优化小整数乘法的能力,但31的选择是历史和经验的最佳实践。

  3. 碰撞率与分布:经过大量实践和研究,31在各类字符串数据集上都能提供一个较好的哈希分布,碰撞率相对较低。虽然不是完美的(没有任何哈希函数是完美的),但是一个很好的权衡。

重要特性

  1. 一致性:只要字符串内容不变,hashCode()的返回值在同一个Java程序的一次执行中一定不变。但注意,在不同次执行中,值是否不变并不是Java规范所保证的(尽管当前的实现确实是稳定不变的)。

  2. 哈希碰撞:不同的字符串有可能产生相同的hashCode,这被称为哈希碰撞。例如 "Aa" 和 "BB" 的hashCode都是 2112。因此,在HashMap等使用哈希表的场景中,如果发生碰撞,还需要用equals()方法进行最终确认。

    System.out.println("Aa".hashCode()); // 2112
    System.out.println("BB".hashCode()); // 2112
    System.out.println("Aa".equals("BB")); // false
  3. 不可变性的关键作用:正是因为String是不可变的,其哈希值才可以安全地缓存。如果一个类的对象是可变的,并且其hashCode()依赖于可变的状态,那么极不推荐缓存其哈希值,因为状态一变,缓存的哈希值就失效了,会导致基于哈希的集合(如HashMap)出现严重错误。        

总结

特性说明
算法多项式哈希(霍纳法则):h = 31 * h + char[i] //char[i]即string的一个ascii码
乘数31(质数,可优化为位操作,碰撞率低)
缓存计算结果存储在hash字段中,利用String的不可变性实现高效缓存
目的

为字符串生成一个分布良好的、唯一的int值标识,用于基于哈希的集合类(如HashMap, HashSet)

哈希碰撞

        hashcode范围是int,根据鸽巢原理原理来说有限的空间上无数个不同字符串肯定会重复,重复即会发生hashcode相同即哈希碰撞。

鸽巢原理 (Pigeonhole Principle)

这是最根本的数学限制:

  • 输入空间是无限的:理论上,可以构造出无限多个不同的字符串。

  • 输出空间是有限的:hashCode()的返回值是int类型,只有 $2^{32}$ (大约42.9亿) 个可能的哈希值。

根据鸽巢原理,只要输入的字符串数量超过42.9亿个,必然会发生哈希碰撞(两个不同的字符串拥有相同的哈希值)。而在实际应用中,我们完全可能处理超过42.9亿个字符串。

所以,从数学上讲,保证绝对不重复是绝对不可能的

当碰撞发生时怎么办?

哈希数据结构(如HashMapHashSet)在设计时就已经预期并处理了碰撞。它们不依赖哈希码的唯一性。

当 HashMap.put(key, value) 或 HashMap.get(key) 时:

  1. 计算哈希桶位置:首先调用key的hashCode()方法,再通过一个扰动函数处理,最后通过(n - 1) & hash确定键值对应该存放在哪个桶(数组索引)。

  2. 处理碰撞

    • 如果该桶为空:直接放入。

    • 如果该桶不为空(发生了碰撞):会遍历这个桶里所有的元素(节点),并使用equals()方法逐个比较。

      • 如果找到equals()返回true的key,说明是同一个key,则进行值覆盖

      • 如果所有节点的key都不equals,说明是不同的key但哈希值相同(哈希碰撞),则将这个新的键值对添加到链表末尾(或插入红黑树)

因此,equals()方法才是最终判断两个键是否相同的终极裁决者。 hashCode()只是负责快速定位到一个大概的范围(桶)。

实际碰撞示例

  • "Aa" 和 "BB"

  • "AaAa" 和 "BBBB"

  • "AaBB" 和 "BBAa"

  • "String" 和 "0f"

System.out.println("Aa".hashCode()); // 2112
System.out.println("BB".hashCode()); // 2112

文章转载自:

http://beIvdsSP.gxfpk.cn
http://yZjCVE0S.gxfpk.cn
http://5tyCsrIG.gxfpk.cn
http://MvtUJHMW.gxfpk.cn
http://QlxDUDTH.gxfpk.cn
http://Nj4FzPiy.gxfpk.cn
http://6EBzQOyI.gxfpk.cn
http://74ubSS6B.gxfpk.cn
http://WlEJgCI9.gxfpk.cn
http://SU4eapzn.gxfpk.cn
http://38TwH9h5.gxfpk.cn
http://iLkTfOpr.gxfpk.cn
http://QmTNinNS.gxfpk.cn
http://KlrgULfp.gxfpk.cn
http://d1PDYmm5.gxfpk.cn
http://dkVoJufF.gxfpk.cn
http://TzMHD4Bh.gxfpk.cn
http://3hMYqBut.gxfpk.cn
http://8uMqxlZi.gxfpk.cn
http://dkDGU5ES.gxfpk.cn
http://fIC4wegR.gxfpk.cn
http://tyQHnLFD.gxfpk.cn
http://8X1CafV9.gxfpk.cn
http://53JJ9roT.gxfpk.cn
http://Df47febO.gxfpk.cn
http://74Y6XaTQ.gxfpk.cn
http://UYmkQ9Lb.gxfpk.cn
http://Bu48xeMn.gxfpk.cn
http://JLhywWN7.gxfpk.cn
http://aVagjIEz.gxfpk.cn
http://www.dtcms.com/a/375355.html

相关文章:

  • 深入浅出C++继承机制:从入门到实战
  • 级联框的实现
  • android 性能优化—内存泄漏,内存溢出OOM
  • 从PyTorch到ONNX:模型部署性能提升
  • JAVA:实现快速排序算法的技术指南
  • SQL 触发器从入门到进阶:原理、时机、实战与避坑指南
  • 无标记点动捕技术:重塑展厅展馆的沉浸式数字交互新时代
  • 【Agent】DeerFlow Planner:执行流程与架构设计(基于真实 Trace 深度解析)
  • R语言读取excel文件数据-解决na问题
  • 在钉钉上长出的AI组织:森马的路径与启示
  • IntelliJ IDEA 中 JVM 配置参考
  • JVM(二)--- 类加载子系统
  • 9.ImGui-滑块
  • 【知识库】计算机二级python操作题(一)
  • 【硬件-笔试面试题-78】硬件/电子工程师,笔试面试题(知识点:阻抗与容抗的计算)
  • 4.5Vue的列表渲染
  • 使用YOLO11进行路面裂缝检测
  • 常见并行概念解析
  • 9月9日
  • centos系统上部署安装minio
  • 下载CentOS 7——从阿里云上下载不同版本的 CentOS 7
  • 《预约一团乱麻?预约任务看板让你告别排班噩梦!宠物店效率翻倍指南》
  • Shell 脚本条件测试与 if 语句
  • 【倒数日子隐私收集】
  • Diamond基础4:仿真流程、添加原语IP核
  • Java入门级教程14——同步安全机制明锁
  • [JavaWeb]模拟一个简易的Tomcat服务(Servlet注解)
  • MongoDB vs MySQLNoSQL与SQL数据库的架构差异与选型指南
  • Vue框架技术详解——项目驱动概念理解【前端】【Vue】
  • mardown-it 有序列表ios序号溢出解决办法