hash值计算方法深思
- 项目中用到了RecyclerView的DiffUtil,其中就涉及到equals来判断两个类变量是否相同,散列结构存储又涉及到了Hash值计算。其中hashCode方法内部如何实现自己定义的hash值。当散列结构存储存储对象时,难免会遇到A与B对象的hash值相同的时候,hash值相同后用equals方法去判断是否是为同一个变量,如果equals返回true,则代表散列结构存储中已经有该对象,所以会舍弃掉;如果equals返回false,则将该对象添加进散列结构存储中。
hashCode与equals的关系
- hashCode相同的情况下,equals不一定返回true。(例如A与B发生hash碰撞,但他们是两者完全不同的对象)。
- 两个对象equals返回true的时候,则hashCode必须返回相同的int值。
- 两个对象hashCode返回不同的int值,equals一定会返回false。
参考文献
String
hasCode方法
- 为了方便理解hashCode的计算,我们下面用从String源码分析一波。
首先我们先看一眼源码,再后面一点点进行分析。
public int hashCode() {
//hash初始化是0
int h = hash;
final int len = length();
if (h == 0 && len > 0) {
for (int i = 0; i < len; i++) {
h = 31 * h + charAt(i);
}
hash = h;
}
return h;
}
- 上面hash值的计算中为什么我们要选择31作为因子进行计算呢?原因选择数字31是因为它是一个奇质数,如果选择一个偶数会在乘法运算中产生溢出,导致数值信息丢失,因为乘二相当于移位运算。选择质数的优势并不是特别的明显,但这是一个传统。同时,数字31有一个很好的特性,即乘法运算可以被移位和减法运算取代,来获取更好的性能:31 * i == (i << 5) - i,现代的 Java 虚拟机可以自动的完成这个优化。
这里参考文献:为什么选择31
- 看完文献可以发现,选择31是尽可能避免在散列存储中计算hash值发生hash碰撞,假如很不幸发生了hash碰撞那又该如何判断String是否完全相同呢?那就要看接下来介绍的equals方法了。
equals方法
- 可以从String方法中观察到,是否需要往常量池需要添加新的String,hashCode与equals都是必不可少的,其中当两个不同的String的hashCode返回值相同时,会用比较传统且安全的方法对比两个对象是否完全相同。这个传统的方法就是逐个比较每个字符是否相同,如果遇到一个不同立即返回false。
public boolean equals(Object anObject) {
if (this == anObject) {
//如果比较的对象是当前对象
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = length();
if (n == anotherString.length()) {
int i = 0;
while (n-- != 0) {
if (charAt(i) != anotherString.charAt(i))
return false;
i++;
}
return true;
}
}
return false;
}
总结
- 学无止境,抽空去学习自己感兴趣的Android源码的知识,一点一滴学习,你会发现在需要用到的时候,你能把很多知识点给串联起来,这才是成长的关键。
网友评论