散列计算就是计算元素应该放在数组的哪个元素里。准确的说是放到哪个链表里面。按照Java的规则,如果你要想将一个对象放入HashMap中,你的对象的类必须提供hashcode方法,返回一个整数值。比如String类就有如下方法:
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; }
注意上面的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进制。这个循环可以抽出来单独作为计算进制的好工具:
public static void main(String[] args) { int[] a={1,0}; System.out.println(calculate(2,a)); } private static int calculate(int radix,int[] a){ int sum = 0; for(int i=0;i<a.length;++i){ sum = sum*radix+a[i]; } return sum; }
静态方法caculate接受radix作为进制基数,数组a模拟要计算的进制的数字,只是注意表面顺序需要一致。比如 01 二进制串,在数组中要按照{0,1}排列。上面的输出结果是1,符合01的真实值。
那么为什么选用31作为基数呢?先要明白为什么需要HashCode.每个对象根据值计算HashCode,这个code大小虽然不奢求必须唯一(因为这样通常计算会非常慢),但是要尽可能的不要重复,因此基数要尽量的大。另外,31*N可以被编译器优化为
左移5位后减1,有较高的性能。其实选用31还是有争议,参考这里。
认为这个东西还是会导致较多的重复,应该用更大的数字。所以,或许将来Java的实现中会有所变化。下面这篇文章介绍了两个结论:
1.基数要用质数
质数的特性(只有1和自己是因子)能够使得它和其他数相乘后得到的结果比其他方式更容易产成唯一性,也就是hash code值的冲突概率最小。
2.选择31是观测分布结果后的一个选择,不清楚原因,但的确有利。
另外,String.hashCode内部会缓存第一次计算的值,因为这是一个final(不可变)类,也就是String对象的内容是不会变的。这能够在多次put到HashMap的场合提高性能,不过似乎用处不多。
总结
以上就是本文关于定义hashcode时使用31系数的原因的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站:
《重写hashCode()和equals()方法详细介绍》
《详解hashCode()和equals()的本质区别和联系》
《Java源码角度分析HashMap用法》
如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!
您好,当我想在不更改hashCode()方法的情况下拥有一个自定义HashSet时,有人可以为我指出正确的方向。用法是拥有一组必须具有不同的一个(或多个)属性的对象。 例如,对于这个类: 我希望有UserNameSet,它只允许包含具有不同名称的用户。我不想覆盖User中的hashCode和equals方法,因为我仍然想区分同名但不同电子邮件的用户。 我想为这一个HashMap重写hashCode
问题内容: 抱歉,这个问题听起来太明显了。 我最近开始探索和学习AngularJS。我已经看了一些不错的教程- 官方文件 w3cSchool 一些不错的网站 ..还有其他一些我见过的。 我并不是说我已经阅读/研究了所有文档。 问题从这里开始- 现在,提一个问题,我发现Controller的定义在一个地方是不同的,而在其他地方则是不同的- 一个定义使用一种数组表示法(不确定官方术语)进行注入: 就是
我试图使用code runner在visual studio代码中编译一个带有experimental::filesystem的项目,但是我甚至无法在终端中编译它。 代码如下,文档中的一个非常简单的测试用法: 使用编译 在code-runner配置中或使用just in-terminal通常无法编译或无法工作。 它提供的错误是: 如果有任何帮助,我将不胜感激。我正在运行linux,并且已经检查过l
FAQs in section [31]: [31.1] 什么是值和/或引用传递,在C++用哪个最好? [31.2] 什么是“虚成员”,如何/为什么在C++中使用? [31.3] 怎么区别虚拟数据和动态数据? [31.4] 应该通常使用数据成员对象指针或者使用“组合”? [31.5] 什么是使用成员对象指针的3个相对性能开销? [31.6] “内联虚函数”的会被“内联”吗? [31.7] 听起来像
问题内容: 我想使用Internet上可用的Android Open Source Project的AnalogClock的源代码制作自定义的AnalogClock类。 我想让时钟设置我想要的时间,而不是当前时间。我没有找到明确的示例,因此这篇文章可能会有用。将源代码复制到新文件后,出现一些错误。以下是AnalogClock的原始源代码: !!错误mContext- >将mContext更改为上下
我试图用函数简化一些代码。目的是使用该函数声明空白序列,以便以后填充。 代码当前在单独的一行上声明每个系列,如下所示: 这种方法工作得很好,但对于许多系列,代码会很长。 我想做以下几点: 然而,当我试图声明series_列表时,它返回NameError:[variable]未定义。有没有办法用空对象(即没有数据,但名称为series1、series2、series1000)填充series_列表?