说一说java里面的hashcode1--Object.hashcode()

hetaoblog 2012-06-28 03:34:37
几个月前写了13篇关于java hashcode的原创文章, 这是第一篇,大家多多包涵;

下次我把其他的也贴过来

http://www.hetaoblog.com/myblogs/post/%E8%AF%B4%E4%B8%80%E8%AF%B4java%E9%87%8C%E9%9D%A2%E7%9A%84hashcode-object-hashcode.jhtml

java里面Object这个类的方法里面,一个重要的方法是hashcode(), 下面是javadoc
public int hashCode()
Returns a hash code value for the object. This method is supported for the benefit of hashtables such as those provided by java.util.Hashtable.
The general contract of hashCode is:

Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hashtables.
As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the JavaTM programming language.)

Returns:
a hash code value for this object.

这段话说了关于hashcode()这个函数的几个意思:
1. 用在Hashtable这个类, 其实其他还有HashMap, HashSet, ConcurrentHashMap等类上,一般类名里面有Hash字样的集合类,基本都要用到hashcode()这个函数
2. 同一个对象,如果用来在equals判断的属性未发生改变,那么在同一个进程里面hashcode()函数的多次调用,始终应该返回同一个整数
3. 如果两个对象用equals方法判断为相同,那么hashcode()也应该返回同样的值; 否则hashtable/hashmap这些类就不能正常工作了
4. 如果两个对象用equals方法判断为不同,hashcode可以一样, 就是所谓的hashcode()冲突; 虽然hashcode()可能冲突,但是冲突会使得hashtable()/hashmap()效率下降,我们在自己重写hashcode()函数的时候需要尽可能的让冲突减少; 事实上,很多情况下hashcode()的冲突是难以避免的;
5. jdk的javadoc说'As much as is reasonably practical', Object.hashcode()会返回不同的值, 通常是返回对象的内存地址;
但是实际上,我在hotspot jdk 1.6.30/winxp下面测试,发现并没有返回对象的地址,并且的确是有冲突的,下面是测试代码和运行结果,

<pre>

for(int i = 0; i < 10000; ++i)

{

Object o = new Object();

os.add(o);

Integer h = o.hashCode();



if((i == 361) || (i == 1886) || (i == 2185) || (i == 1905))

{

System.out.println("i,hashcode(): " + i + "," + h);

}



if(s.contains(h))

{

System.out.println("conflict:" + i + ", " + m.get(h));

}

else

{

s.add(h);

m.put(h, i);

}



}



System.out.println(s.size());



int c = 0;

for(Object o: os)

{

c++;

}



System.out.println(c);

}


</pre>

我运行了两次,结果分别是
i,hashcode(): 361,9578500
i,hashcode(): 1886,9578500
conflict:1886, 361
i,hashcode(): 1905,14850080
i,hashcode(): 2185,14850080
conflict:2185, 1905
9998



i,hashcode(): 361,5462872
i,hashcode(): 1886,29705835
conflict:1887, 362
i,hashcode(): 1905,9949222
i,hashcode(): 2185,2081190
conflict:2186, 1906
9998
10000

说明:
1. 代码最后的 for(Object o: os)里面对count统计出10000,表示所有对象都没有被回收,也就不可能存在同一个地址被两次分配空间的情况
2. 在这个10000的循环中,每次都有两组对象发生冲突,说明在这个版本的jdk里面,Object.hashcode()肯定没有返回对象的地址;
...全文
150 3 打赏 收藏 转发到动态 举报
写回复
用AI写文章
3 条回复
切换为时间正序
请发表友善的回复…
发表回复
hetaoblog 2012-07-02
  • 打赏
  • 举报
回复
前一篇文章说一说java里面的hashcode-string-hashcode, 这篇再多说一下这个String.hashcode(), 我看到String.hashcode()的冲突的时候,第一个想法就是,哈,那不是质数31取的太小了么?导致在常用ascii字符集里面容易冲突,那么,直接设个大点的质数不就好了么?幸运的是,257就是个质数,那么就用257,立刻就可以避免之前的冲突的例子了,下面是一段代码 @Test public void testBetterhash() { System.out.println(betterHash("Aa") + "," + betterHash("BB")); System.out.println(betterHash("Ba") + "," + betterHash("CB")); System.out.println(betterHash("Ca") + "," + betterHash("DB")); System.out.println(betterHash("Da") + "," + betterHash("EB")); } public static int betterHash(String s) { int h = 0; int len = s.length(); for (int i = 0; i < len; i++) { h = 257*h + s.charAt(i); } return h; } 返回的结果很好,全部不冲突 16802,17028 17059,17285 17316,17542 17573,17799 不过悲剧的是,该算法相当于以257为进制,每次乘以257至少相当于移了8位多,在32位jdk下面,int只有32位,遇到5个字符就溢出了,调用betterHash("abcde")就会返回-372058641 所以,这个想法自然就失败了; 那么,原来的String.hashcode()算法在‘通常情况’下大约有多少的冲突概率呢? 为此,我从 http://www.mieliestronk.com/wordlist.html
hetaoblog 2012-06-29
  • 打赏
  • 举报
回复
http://www.hetaoblog.com/myblogs/post/%E8%AF%B4%E4%B8%80%E8%AF%B4java%E9%87%8C%E9%9D%A2%E7%9A%84hashcode-string-hashcode.jhtml
前一篇文章说了Object.hashcode(),现在来看下String.hashcode(), 因为很多情况下HashMap和HashTable的key是String;
下面是jdk里面的代码和注释

Returns a hash code for this string. The hash code for a String object is computed as
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

using int arithmetic, where s[i] is the ith character of the string, n is the length of the string, and ^ indicates exponentiation. (The hash value of the empty string is zero.)




public int hashCode() {

int h = hash;

int len = count;

if (h == 0 && len > 0) {

int off = offset;

char val[] = value;



for (int i = 0; i < len; i++) {

h = 31*h + val[off++];

}

hash = h;

}

return h;

}





这个意思是说

1. String.Hashcode()的算法是s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1],就是每个字符的ascii值以质数31作为进制计算后得到的结果就是,相当于31进制;

实际的计算过程是从左向右计算,每移一位对之前算的值乘以31

2. 同时,有个成员变量hash保存了计算过的hashcode(), 第一次调用String.hashcode()后,计算出来的hashcode()就保存在hash成员变量中,以后就直接返回了; 当然,这里有个特殊的地方是String是常量,就是创建以后值就不再变了, "Strings are constant; their values cannot be changed after they are created.", 这是jdk里面的说明;所以他的hashcode()也永远不会变化,可以缓存在成员变量里面

3. 既然String.hashcode()的算法已经明确了,那么就可以尝试构造冲突,

假设两个字符串都是有两个字符,那么如果满足 a1*31+b1 = a2*31 +b2这个等式,这两个字符串的hashcode()就一样

也就是(a1-a2)*31=b2-b1 ,如果设a1-a2=1, 那么b2-b1=31就好, ascii表里面满足这个等式的字符串可以找出很多来, 下面是一段测试代码





public void testHash()



{



System.out.println("A:" + (int)'A');



System.out.println("B:" + (int)'B');



System.out.println("a:" + (int)'a');



System.out.println("Aa".hashCode() + "," + "BB".hashCode());



System.out.println("Ba".hashCode() + "," + "CB".hashCode());



System.out.println("Ca".hashCode() + "," + "DB".hashCode());



System.out.println("Da".hashCode() + "," + "EB".hashCode());



}





打印的结果

A:65

B:66

a:97

2112,2112

2143,2143

2174,2174

2205,2205

可以看到,这些字符串都满足上面的等式,都发生了hashcode()冲突的情况; 并且,只要按照上面的方法,可以构造出很多两个字符串的冲突来;

更进一步,假设两个字符串都有4个字符串,按照公式,假设前两个字符串产生hashcode为m, 后两个字符串产生hashcode为n,

那么,4个字符串的hashcode是m*31^2+n, 所以,如果两个字符串组成的hashcode()是一样的,那么4个字符串组成的hashcode也是一样的!

所以,"AaAa", "BBBB","AaBB","BBAa"的hashcode都一样,都是2031744,

而"CaDB","CaCa","DBCa","DBDB"的hashcode都一样,是2091388;

并且按照这个方法,可以构造6个字符串,8个字符串等hashcode相同的字符串来;

去年的hash冲突导致dos攻击的漏洞,基本就可以按照这个方法构造错误

酷壳的Hash Collision DoS 问题里面也详细说明了当时的情况,不过陈浩举例的hash函数其实是HashMap的hash函数,

那个其实是对已经产生的hashcode()做二次hash,并不是String.hashcode()的代码,


可能读者看那个代码和后续的说明不能直接看明白为什么Aa和BB的hash值是一样的,也不知道为什么进一步可以得出AaBB和AaAa的hash值也一样的

相信读者看了这篇文章应该会清楚了

当然,陈浩的技术很好,我一直都很喜欢看他的文章

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧