hashCode()函数详解
来源:互联网 发布:打印文件软件 编辑:程序博客网 时间:2024/06/06 07:50
上一篇文章介绍了==
与equals()
的区别,在其中提到了重写equals()
的同时需要重写hashCode()
函数,本篇文章主要是对hashCode()
做一个详细的介绍,包括其存在的意义以及如何去重写hashCode()
.
hashCode()存在的意义
在Java的Object类中有一个方法hashCode()
public native int hashCode();
但是hashCode()
函数存在的意义是什么呢?先举一个例子,假如一个列表中存储了十万个对象,现在我们需要往其中插入一个对象A,若列表中已经存在了一个与A相等的对象,则不进行插入,否则就插入到列表中,也许我们很快就会想到equals()
,调用equals()
(这里假设已经重写了equals()
)来比较对象是否相等,然后执行插不插入的操作。初一看没错,能实现需求呀!但是回过头一想,十万条数据逐个去调用equals()是不是相等,性能就不用说了哈!
为了解决此类问题,散列集合就诞生了,原理则是通过对象生成一个key,然后再通过内部映射到集合的某个位置,当这个位置上已经存在对象的时,调用equals()
来进行比较是否真正相等,若相等则说明存在,不相等说明不存在,然后按照集合的具体存储结构来进行存储。
散列集合中,判断集合中是否存在相等的对象时,需要经过3个步骤:
1.通过对象生成一个key
2.将key在内部映射到一个具体的位置
3.与步骤2映射的位置上的对象进行比较(调用equals()
)
在Java中散列集合包括HashSet
、HashMap
以及HashTable
,而其中的key就是通过对象的hashCode()
函数来生成。
由上面的3个步骤我们可以知道,当不相等的对象生成的key不同时,每个内部映射的位置都不一样,则不需要多次调用equals()
进行比较,而不相等的对象可能生成相等的key(hash冲突),这种概率越大,则需要调用equals()
的进行比较的次数越多,效率就越低,因此我们要尽可能做到不相等的对象的hashCode()
生成的key不相等。
如何重写hashCode()
通过上面的分析,重写hashCode()
函数需要注意两点:
1.不相等的对象的hashCode()
生成的key要尽可能不一样。
2.相等的对象的hashCode
生成的key必须一样(这就是重写equals()
必须重写hashCode()
的原因所在)
因此hashCode()的重写完全取决了equals()的实现,而equals()的实现最终会回到八种基本数据类型的比较,只要清楚了基本类型如何去实现hashCode(),再复杂的hashCode()重写的原理都是一样的,都能迎刃而解。
而hashCode()的返回类型是int型,因此其它的基本类型都需要在hashCode()转换为int.下面介绍其他基本类型如何来重写hashCode.
byte,short,int,char
byte、short、char三种类型转换为int时,可以直接进行转换且没有精度的损失,实现原理是一样的,看下面的例子
public class User { short id; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return id == user.id; }}
上面是一个user对象,重写了equals(),其中通过比较short类型的id来判断两个user对象是否相等,根据重写hashCode()的原则,两个相等对象的hashCode()生成的key必须相等,不相等对象的hashCode()生成的对象尽可能不相等,因为是通过id来判断是否相等,因此hashCode()只需要直接将short类型的id转换为int类型返回即可满足。具体重写的hashCode()实现如下
public class User { short id; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return id == user.id; } @Override public int hashCode() { return (int) id; }}
byte跟short实现方式完全一样,而int类型不需要转换直接返回即可,具体代码就不贴了。下面看一个稍微复杂一点的。
long
long类型转换为int类型会存在精度的损失,将高位直接丢掉,那么重写hashCode()是否也是直接转换为int类型呢?答案是可以的,满足重写hashCode()的两个原则,将上面的id改为long类型,代码如下
public class User { long id; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return id == user.id; } @Override public int hashCode() { return (int) id; }}
代码跟重写short、byte重写hashCode()代码是完全一样的,但是因为long转int会将高32位直接丢掉,导致高位不同、低位相同的id的hashCode()是一样的
public static void testLongHashCode() { User user1 = new User(); user1.id = 0b0000_0000_1111_1111_1100_0011_0010_1100_0011_1111_1111_0000_1100_0011_0010_1100L; User user2 = new User(); user2.id = 0b0000_0000_1000_1000_1100_0011_0010_0101_0011_1111_1111_0000_1100_0011_0010_1100L; Log.v("hashcode","user1 hashCode:" + user1.hashCode()); Log.v("hashcode","user2 hashCode:" + user2.hashCode());}
上面的代码中,user1和user2的id的高32位不一样、低32位一样,分别打印他们的hashCode()生成的值,结果如下
V/hashcode: user1 hashCode:1072743212V/hashcode: user2 hashCode:1072743212
user1和user2的hashCode()生成的值是一样的,而重写hashCode()的原则中,不相等的对象hashCode()生成的key要尽可能不一样,为了避免高32位相同、低32位不同hashCode()生成的key一样,推荐的做法是将高32位和低32位做异或运算,修改User的hashCode的代码
public class User { public long id; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return id == user.id; } @Override public int hashCode() { return (int)(id ^ (id >>> 32)); }}
稍微解释下,id >>> 32 无符号右移32位,高32位变成低32位,再与原有的id进行异或运算,再强转int取低32位,
还是上面的user1和user2,看看生成的hashCode()是否还一样,运行的结果如下
V/hashcode: user1 hashCode:1057947648V/hashcode: user2 hashCode:1064828937
hashCode()生成的key不一样了,那么这种方法是否一种完美的方案呢?那user1的id不变,修改user2的id试试,其中两个id不相等,代码如下
public static void testLongHashCode() { User user1 = new User(); user1.id = 0b0000_0000_1111_1111_1100_0011_0010_1100_0011_1111_1111_0000_1100_0011_0010_1100L; User user2 = new User(); user2.id = 0b0000_0000_1111_1111_1100_0011_0010_0000_0011_1111_1111_0000_1100_0011_0010_0000L; Log.v("hashcode","user1 hashCode:" + user1.hashCode()); Log.v("hashcode","user2 hashCode:" + user2.hashCode());}
运行结果如下
V/hashcode: user1 hashCode:1057947648V/hashcode: user2 hashCode:1057947648
哈哈,结果又一样了,其实我们是进行高32位与低32位运算的结果,因此只要对应位上同时发生变化后的结果一样,那么最后hashCode()生成的值还是一样的。
那么针对于long的重写hashCode(),是否能避免生成一样的结果呢?答案是不可能的,因为hashCode()的返回类型为int,long到int的转换存在精度损失,产生冲突是不可避免的(hash冲突)。
boolean
布尔类型的hashCode()重写就简单了,因为不是true就是false,直接转换为1或者0就好了,代码如下
public class User { public boolean id; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return id == user.id; } @Override public int hashCode() { return (id ? 1 : 0); }}
Float 与 Double
Float重写hashCode() 直接将float转换为int即可,而double则先转换为long,再通过long 高地位异或的方式实现
Float
public class User { public float id; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return id == user.id; } @Override public int hashCode() { return (id != +0.0f ? Float.floatToIntBits(id) : 0); }}
Double
public class User { public double id; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return id == user.id; } @Override public int hashCode() { long temp = Double.doubleToLongBits(id); return (int) (temp ^ (temp >>> 32)); }}
当然equals()的实现可能比较复杂,常见的可能是数组的比较、递归之类的,对于数组的比较则需要逐个进行计算,而递归的话则需要进行递归的计算
数组
String就是一个典型的char数组,那么我们分析分析String的hashCode()是如何实现的呢?
@Override public int hashCode() { int hash = hashCode; if (hash == 0) { if (count == 0) { return 0; } for (int i = 0; i < count; ++i) { hash = 31 * hash + charAt(i); } hashCode = hash; } return hash; }
通过遍历每一个字符然后计算得出结果,对代码稍微解释一下
为什么要使用“*”?
主要是为了使散列值依赖于域的顺序,如果不适用的话,那么“as”与“sa”的hashCode()生成的值就是一样的了。
为什么是乘以31而不是其他数字?
乘以31不是为了别的,而是31这个数字的特殊性,因为任何数n * 31就可以被JVM优化为 (n << 5) -n,移位和减法的操作效率要比乘法的操作效率高的多。
其他
其他的复杂的equals()实现,最终会回到基本类型的比较来,只要搞懂了基本类型如何去重写hashCode(),其他都会迎刃而解。
- hashCode()函数详解
- HashCode详解
- equals() 和hashcode()详解
- java hashCode详解
- hashCode与equals详解
- hashcode()和equals()详解
- hashCode和equals详解
- java hashCode详解
- Java中hashCode详解
- Java HashCode 详解
- Java HashCode详解
- java hashCode详解
- java的hashcode详解
- java hashCode详解
- equal和hashcode详解
- hashcode与equals详解
- hashCode与equals详解
- Java之hashCode()函数
- C++中srand函数与rand函数产生一定范围内的随机数
- Android在自己的应用中启动第三方应用
- 数据库表空间不足
- C/C++ — 实现日期计算器
- Android应用如何适配不同的屏幕
- hashCode()函数详解
- NSOperation浅析
- MYSQL
- 模态框回车搜索的实现
- Linux C多线程
- 判断二进制数除以3的余数
- ffmpeg录制屏幕,生成MP4视频文件开发过程
- iOS中的runtime源码简要分析(一)
- EditText显示隐藏密码