String源码分析(1)--哈希篇
本文基于JDK1.8,首发于公众号:Plus技术栈
让我们从一段代码开始
System.out.println("a" + "b" == "ab");
System.out.println(new String("ab") == "ab");
上述代码中,第一行结果为True,第二行结果为False。两者结果不同的原因在于Java中的==符号判断的是对象是否相等,其实质上是比较两者的内存地址,很显然第一行两边指向同一对象,而第二行指向不同对象。
我们都知道String中判断字符串的字面值是否相等要用equals()方法而不是==,那么String类中equals()究竟是如何实现的呢?
String类会在后续文章中分为几个篇章来讲。本篇文章中主要深入String类中有关哈希的部分,主要有以下几个要点:
equals()方法的实现
哈希值、字面值与内存地址之间的关系
哈希碰撞与生日攻击
equals()方法的实现
equals的实现
String类属于“值类“。程序员在比较字符串时,希望知道它们在逻辑上是否相等,而不是想了解它们是否指向同一个对象.
值类仅仅是一个表示值的类,具有自己特有的“逻辑相等“概念(不同于对象等同的概念),因此为了满足比较的需求,String类需要覆盖Object类的equals()方法。
每个值至多只存在一个对象的“值类”不需要覆盖equals方法。如枚举类型
String的equals()实现如下:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
逻辑很简单,如果比较的两个字符串指向同一对象或者每个位置上的字符相等,则返回true。以上实现遵守了Object的规范[JavaSE6]:
- 自反性。对于任何非null的引用值x,x.equals(x)必须返回true。
- 对称性。对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
- 传递性。对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)d也必须返回true。
- 一致性。对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true,或者一致地返回false
好吧,以上4条规范又让我想起大学被数学分析支配的恐惧。虽然恐惧,但是我们不能忽视这四条规定,否则我们的程序可能会莫名其妙的崩溃。
hashcode的实现
那么,在覆盖了Object.equals()方法之后是否就可以进行比较了呢?回答当然是不!
覆盖equals时总要覆盖hashCode
经常看源码的同学会发现,很多类中都有hash相关的变量,本篇文章涉及的String类中也有hashcode()方法,那么哈希到底是什么呢?
所谓哈希(hash),就是将不同的输入映射成独一无二的、固定长度的值(又称"哈希值")。它是软件常见运算之一。如果我们在覆盖了equals()方法之后没有覆盖hashcode()将会导致String类无法结合所有基于散列的集合一起正常运作,这样的集合包括HashMap、HashSet和Hashtable。
无法运作的原因在于我们违反了一条关键约定:相等的对象必须具有相等的散列码(hash code)。
一起看看String类中的hashcode()方法:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
一个String实例的hash值只与其内容有关。从代码实现来看,String类计算哈希值实际上就是通过公式(s代表字符串,n代表字符串长度):
s[0]31^(n-1) + s[1]31^(n-2) + ... + s[n-1]
计算而来,即对字符串的每个取31的n-1次幂并累加。那么这个31是怎么来的呢
在Java Effective中有提及:
之所以选择31,是因为它是一个奇素数。如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算。使用素数的好处并不明显,但是习惯上都使用素数来计算散列结果。31有个很好的特性。即用移位和减法来代替乘法,可以得到更好的性能:31 * i == (i << 5) - i。现代的VM可以自动完成这种优化。
一个好的散列函数通常倾向于“为不相等的对象产生不相等的散列码“。理想情况下,散列函数应该把集合中不相等的实例均匀地分布到所有可能的散列值上。
31是一个奇素数,如果使用非奇素数,非奇素数代表着乘以偶数会导致较少的位包含“变化”信息,即若低位变为零,乘完之后还是零。用偶数会失去一点“可变性”。结果是可能的哈希值分布较差。
除此之外。31可以保证比较少的哈希碰撞, 31i=32i-i=(i<<5)-i,这种位移与减法结合的计算相比一般的运算快很多。
哈希值、字面值与内存地址之间的关系
在hashcode()函数的源码实现可以看到,一个字符串实例的hash值仅与其内容有关。那么是否可以理解内容相同的两个字符串的哈希值一定相等呢?请看以下代码:
int a = "Aa".hashCode();
int b = "BB".hashCode();
a与b的值相等,都是2112。上面说到,哈希值就是不同的输入映射成独一无二的、固定长度的值。如果不同的输入得到了相同的哈希值,就发生了“哈希碰撞“(collision)。
像上面这种情况就发生了哈希碰撞,除了这种简单字符串之外,"柳柴"与"柴柕","志捘"与"崇몈"也会导致碰撞。两者的hashcode值分别为851553和786017。
我们可以总结一下:
- 哈希值相同的字符串,字面值不一定相同。
- 字面值相同的字符串,哈希值一定相同。
- 字面值相同的字符串,内存地址不一定相同。
哈希碰撞和与生日攻击
防止哈希碰撞
前面说了那么多的哈希碰撞,究竟我们应该如何防止哈希碰撞?
防止哈希碰撞的最有效方法,就是扩大哈希值的取值空间。
16个二进制位的哈希值,产生碰撞的可能性是 65536 分之一。也就是说,如果有65537个用户,就一定会产生碰撞。哈希值的长度扩大到32个二进制位,碰撞的可能性就会下降到 4,294,967,296 分之一。
更长的哈希值意味着更大的存储空间、更多的计算,将影响性能和成本。开发者必须做出抉择,在安全与成本之间找到平衡。
下面就介绍,如何在满足安全要求的前提下,找出哈希值的最短长度。
生日攻击
哈希碰撞的概率取决于两个因素(假设哈希函数是可靠的,每个值的生成概率都相同)。
- 取值空间的大小(即哈希值的长度)。
- 整个生命周期中,哈希值的计算次数。
这个问题在数学上早有原型,叫做"生日问题"(birthday problem):一个班级需要有多少人,才能保证每个同学的生日都不一样?
答案很出人意料。如果至少两个同学生日相同的概率不超过5%,那么这个班只能有7个人。事实上,一个23人的班级有50%的概率,至少两个同学生日相同;50人班级有97%的概率,70人的班级则是99.9%的概率(计算方法见后文)。
这意味着,如果哈希值的取值空间是365,只要计算23个哈希值,就有50%的可能产生碰撞。也就是说,哈希碰撞的可能性,远比想象的高。哈希碰撞所需耗费的计算次数,跟取值空间的平方根是一个数量级
这种利用哈希空间不足够大,而制造碰撞的攻击方法,就被称为生日攻击(birthday attack)。
有关哈希碰撞和生日攻击的知识与推导就不在这里详细描述了,大家感兴趣的可以去找找资料。
String源码分析(1)--哈希篇的更多相关文章
- (转)Java中的String为什么是不可变的? -- String源码分析
背景:被问到很基础的知识点 string 自己答的很模糊 Java中的String为什么是不可变的? -- String源码分析 ps:最好去阅读原文 Java中的String为什么是不可变的 什 ...
- 鸿蒙内核源码分析(忍者ninja篇) | 都忍者了能不快吗 | 百篇博客分析OpenHarmony源码 | v61.02
百篇博客系列篇.本篇为: v61.xx 鸿蒙内核源码分析(忍者ninja篇) | 都忍者了能不快吗 | 51.c.h.o 编译构建相关篇为: v50.xx 鸿蒙内核源码分析(编译环境篇) | 编译鸿蒙 ...
- 鸿蒙内核源码分析(汇编传参篇) | 如何传递复杂的参数 | 百篇博客分析OpenHarmony源码 | v23.02
百篇博客系列篇.本篇为: v23.xx 鸿蒙内核源码分析(汇编传参篇) | 如何传递复杂的参数 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...
- 鸿蒙内核源码分析(用栈方式篇) | 程序运行场地谁提供的 | 百篇博客分析OpenHarmony源码 | v20.04
百篇博客系列篇.本篇为: v20.xx 鸿蒙内核源码分析(用栈方式篇) | 程序运行场地谁提供的 | 51.c.h .o 精读内核源码就绕不过汇编语言,鸿蒙内核有6个汇编文件,读不懂它们就真的很难理解 ...
- 鸿蒙内核源码分析(内存主奴篇) | 皇上和奴才如何相处 | 百篇博客分析OpenHarmony源码 | v10.04
百篇博客系列篇.本篇为: v10.xx 鸿蒙内核源码分析(内存主奴篇) | 皇上和奴才如何相处 | 51.c.h .o 前因后果相关篇为: v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 ...
- MyBatis源码分析之环境准备篇
前言 之前一段时间写了[Spring源码分析]系列的文章,感觉对Spring的原理及使用各方面都掌握了不少,趁热打铁,开始下一个系列的文章[MyBatis源码分析],在[MyBatis源码分析]文章的 ...
- v80.01 鸿蒙内核源码分析(内核态锁篇) | 如何实现快锁Futex(下) | 百篇博客分析OpenHarmony源码
百篇博客分析|本篇为:(内核态锁篇) | 如何实现快锁Futex(下) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...
- spring源码分析之spring-core总结篇
1.spring-core概览 spring-core是spring框架的基石,它为spring框架提供了基础的支持. spring-core从源码上看,分为6个package,分别是asm,cgli ...
- string源码分析 ——转载 http://blogs.360.cn/360cloud/2012/11/26/linux-gcc-stl-string-in-depth/
1. 问题提出 最近在我们的项目当中,出现了两次与使用string相关的问题. 1.1. 问题1:新代码引入的Bug 前一段时间有一个老项目来一个新需求,我们新增了一些代码逻辑来处理这个新需求.测试阶 ...
随机推荐
- webuploader项目中多文件上传实例
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- 低性能3张图片轮播React组件
import React from 'react'; import {getSwipeWay} from '../utils/swipe'; class Carousel extends React. ...
- (60)zabbix网络发现介绍Network Discovery
网络发现简介 网络发现有什么用?网络发现怎么配置? 我们带着这两个问题开始我们的网络发现之旅. 比如小明有100台服务器,不想一台台主机去添加,能不能让zabbix自动添加主机呢,当然可以,网络发现便 ...
- linux uptime-查看Linux系统负载信息
更多linux 性能监测与优化 关注:linux命令大全 uptime命令能够打印系统总共运行了多长时间和系统的平均负载.uptime命令可以显示的信息显示依次为:现在时间.系统已经运行了多长时间.目 ...
- 使用POI循环写入数据时发现只有最后一列有数据
直接说重点: 一般出现在多重(一般是两重)循环,再多几重效率就很低了. 检查调用createRow() 是否在外层循环调用的,如果是在内层循环调用就会出现这个问题,因为程序会不停的重新创建行,直至最后 ...
- java各种数据库连接
MySQL: String Driver="com.mysql.jdbc.Driver"; //驱动程序 String URL="jdbc:mys ...
- initcall机制
参考:initcall机制 /* include/linux/init.h: */ /* For assembly routines */ #define __HEAD .section " ...
- Ubuntu 16.04如何使用无线网卡上网
我使用的无线网卡卡托型号是华为E8372h,网卡是普通电信卡(既可以打电话也可以上网). 按照“芯片朝上.缺口朝外.用最大卡”的方法将网卡装入卡托后,紧接着便将卡托插入笔记本对应的USB接口中. 在这 ...
- 一款基于jQuery的QQ表情插件
我们在QQ聊天或者发表评论.微博时,会有一个允许加入表情的功能,点击表情按钮,会弹出一系列表情小图片,选中某个表情图片即可发表的丰富的含表情的内容.今天和大家分享一款基于jQuery的QQ表情插件,您 ...
- 【机房收费系统 4】:VB获取标准北京时间,免除时间误差
导读:这又是师傅给我指出的一个问题,说实话,其实开始根本没有当回事,觉得麻烦,可是,等我完成了获取标准北京时间后,我发现,这一步,是必须的.谢谢师傅对我的严格要求,让我一步一步的成长起来! 一.事件缘 ...