为什么在定义hashcode时要使用31这个数呢?

简介: 散列计算就是计算元素应该放在数组的哪个元素里。准确的说是放到哪个链表里面。按照Java的规则,如果你要想将一个对象放入HashMap中,你的对象的类必须提供hashcode方法,返回一个整数值。比如String类就有如下方法: [java] view plaincopyprint? public int hashCode() {

散列计算就是计算元素应该放在数组的哪个元素里。准确的说是放到哪个链表里面。按照Java的规则,如果你要想将一个对象放入HashMap中,你的对象的类必须提供hashcode方法,返回一个整数值。比如String类就有如下方法:

[java]  view plain copy print ?
  1. public int hashCode() {  
  2.         int h = hash;  
  3.         int len = count;  
  4.         if (h == 0 && len > 0) {  
  5.             int off = offset;  
  6.             char val[] = value;  
  7.   
  8.             for (int i = 0; i < len; i++) {  
  9.                 h = 31*h + val[off++];  
  10.             }  
  11.             hash = h;  
  12.         }  
  13.         return h;  
  14.     }  

注意上面的for循环,有点搞吧?我来举个例子,让你很容易明白它在搞什么名堂。比如有一个字符串“abcde”,采用31进制的计算方法来计算这个字符串的总和,你会写出下面的计算式子:
a*31^4+b*31^3+c*31^2+d*31^1+e*31^0.注意,这里的a,b,c,d或者e指的是它们的ASCII值。很有趣的循环,居然可以用来算N进制。这个循环可以抽出来单独作为计算进制的好工具:

[java]  view plain copy print ?
  1. public static void main(String[] args) {  
  2.         int[] a={1,0};  
  3.         System.out.println(calculate(2,a));  
  4.     }  
  5.   
  6.     private static int calculate(int radix,int[] a){  
  7.         int sum = 0;  
  8.         for(int i=0;i<a.length;++i){  
  9.             sum = sum*radix+a[i];  
  10.         }  
  11.         return sum;  
  12.     }  

  静态方法caculate接受radix作为进制基数,数组a模拟要计算的进制的数字,只是注意表面顺序需要一致。比如 01 二进制串,在数组中要按照{0,1}排列。上面的输出结果是1,符合01的真实值。
  那么为什么选用31作为基数呢?先要明白为什么需要HashCode.每个对象根据值计算HashCode,这个code大小虽然不奢求必须唯一(因为这样通常计算会非常慢),但是要尽可能的不要重复,因此基数要尽量的大。另外,31*N可以被编译器优化为
左移5位后减1,有较高的性能。其实选用31还是有争议,反对者(参考 http://stackoverflow.com/questions/299304/why-does-javas-hashcode-in-string-use-31-as-a-multiplier)
认为这个东西还是会导致较多的重复,应该用更大的数字。所以,或许将来Java的实现中会有所变化。下面这篇文章介绍了两个结论:
1.基数要用质数
质数的特性(只有1和自己是因子)能够使得它和其他数相乘后得到的结果比其他方式更容易产成唯一性(最终出来的结果只能被素数本身和被乘数还有1来整除),也就是hash code值的冲突概率最小。

2.选择31是观测分布结果后的一个选择,不清楚原因,但的确有利。

3.31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化.(提高算法效率)

http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/

另外,String.hashCode内部会缓存第一次计算的值,因为这是一个final(不可变)类,也就是String对象的内容是不会变的。这能够在多次put到HashMap的场合提高性能,不过似乎用处不多。

目录
相关文章
|
8月前
判断变量是否为数组的几种方法
判断变量是否为数组的几种方法
91 0
|
3月前
|
存储 C语言
学习总结(位操作符;循环输入的三种方式;交换两个变量值的三种方法;打印数字对应的二进制;unsigned int 与int 的区别;改变特定位数0/1;&&和||的连续操作(与前置,后置结合))
学习总结(位操作符;循环输入的三种方式;交换两个变量值的三种方法;打印数字对应的二进制;unsigned int 与int 的区别;改变特定位数0/1;&&和||的连续操作(与前置,后置结合))
31 0
|
4月前
对调 2个变量的值若干种方式
对调 2个变量的值若干种方式
11 0
|
10月前
|
安全
运算符:指数-链判断-Null判断-逻辑赋值
运算符:指数-链判断-Null判断-逻辑赋值
52 0
|
Java
Java经典编程习题100例:第19例:要求定义一个int型数组a,包含100个元素,保存100个随机的4位数。再定义一个 int型数组b,包含10个元素。统计a数组中的元素对10求余等于0的个数,保
Java经典编程习题100例:第19例:要求定义一个int型数组a,包含100个元素,保存100个随机的4位数。再定义一个 int型数组b,包含10个元素。统计a数组中的元素对10求余等于0的个数,保
225 0
求字符串的长度(4种写法)(普通写法,函数写法(两种:有无返回值),不允许创建临时变量法(递归))
求字符串的长度(4种写法)(普通写法,函数写法(两种:有无返回值),不允许创建临时变量法(递归))
116 0
求字符串的长度(4种写法)(普通写法,函数写法(两种:有无返回值),不允许创建临时变量法(递归))
|
算法 Go
算法练习第十题——寻找重复数(不修改数组)
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
Java练习——方法案例(较大数、带参数、带返回值、方法重载、数组遍历、)需求、分析、代码
Java练习——方法案例(较大数、带参数、带返回值、方法重载、数组遍历、)需求、分析、代码!
|
存储 缓存 Java
Java 对象的哈希值是每次 hashCode() 方法调用重计算么?
Java 对象的哈希值是每次 hashCode() 方法调用重计算么?
Java 对象的哈希值是每次 hashCode() 方法调用重计算么?