网上一般都说hashCode 就是对象的内存地址,但是你想下垃圾回收时(复制算法,整理算法)都要发生对象移动,都要改变对象的内存地址。但hashCode又不能变化,那么该值一定是被保存在对象的某个地方了。
hashcode就是保存在对象头里面的,但是如果hashcode是内存地址的话,那么就有可能会重复到之前回收对象的地址。
苍天可见,hashcode绝对不是简简单单的内存地址。
hashcode的6种生成策略
可以通过在JVM启动参数中添加-XX:hashCode=4,改变默认的hashCode计算方式。
1. hashCode=0
if (hashCode == 0) {
value = os::random() ;
}
此类方案返回一个Park-Miller伪随机数生成器生成的随机数OpenJdk 6 &7的默认实现。
2. hashCode=1
if (hashCode == 1) {
intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
}
此类方案将对象的内存地址,做移位运算后与一个随机数进行异或得到结果。
3. hashCode = 2
if (hashCode == 2) {
value = 1 ; // for sensitivity testing
}
此类方案返回固定的1。
4. hashCode = 3
if (hashCode == 3) {
value = ++GVars.hcSequence ;
}
此类方案返回一个自增序列的当前值。
5. hashCode = 4
if (hashCode == 4) {
value = cast_from_oop<intptr_t>(obj) ;
}
此类方案返回当前对象的内存地址。
6. hashCode 为 其它
通过和当前线程有关的一个随机数+三个确定值,运用Marsaglia’s xorshift scheme随机数算法得到的一个随机数。JDK8 的默认hashCode的计算方法就是这个xorshift 算法。
测试hashcode是不是内存地址
首先有个工具类:利用Unsafe类 打印真实内存地址
public static class Memory {
static final Unsafe unsafe = getUnsafe();
static final boolean is64bit = true;
public static void printAddresses(Object... objects) {
long last = 0;
int offset = unsafe.arrayBaseOffset(objects.getClass());
int scale = unsafe.arrayIndexScale(objects.getClass());
switch (scale) {
case 4:
long factor = is64bit ? 8 : 1;
final long i1 = (unsafe.getInt(objects, offset) & 0xFFFFFFFFL) * factor;
System.out.print(Long.toHexString(i1));
last = i1;
for (int i = 1; i < objects.length; i++) {
final long i2 = (unsafe.getInt(objects, offset + i * 4) & 0xFFFFFFFFL) * factor;
if (i2 > last)
System.out.print(", +" + Long.toHexString(i2 - last));
else
System.out.print(", -" + Long.toHexString(last - i2));
last = i2;
}
break;
case 8:
throw new AssertionError("Not supported");
}
System.out.println();
}
private static Unsafe getUnsafe() {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
return (Unsafe)theUnsafe.get(null);
} catch (Exception e) {
throw new AssertionError(e);
}
}
}
main方法
public static void main(String[] args) throws Exception {
Object o = new Object();
Memory.printAddresses(o);
System.err.println(Long.toHexString(o.hashCode()));
System.err.println(Long.toHexString(System.identityHashCode(o)));
}
打印结果
76b8b4c28
4e25154f
4e25154f
hashCode 跟 System.identityHashCode 方法一样,但是跟内存地址没半毛钱关系。
我们在改变main方法
public static void main(String[] args) throws Exception {
Object o1 = new Object();
Object o2 = new Object();
Memory.printAddresses(o2);
System.err.println(Long.toHexString(o2.hashCode()));
System.err.println(Long.toHexString(System.identityHashCode(o2)));
}
打印结果
76b8b4c58
4e25154f
4e25154f
很奇怪,我们定义了两个对象,打印第二个对象内存地址和第一次打印的不同,这很正常。
但是打印hashcode确实跟第一次完全一样,由此可见hashcode值跟对象没关系,可能就是分配好的数列,你构造了一个对象就顺序取取数列中的数作为hashcode。
hashcode什么时候生成,在哪分配给对象的 还请大神知道的留言给我!!!!谢谢~~
再来欣赏几个面试题
1、equals()既然已经能实现对比的功能了,为什么还要hashCode()呢?
因为重写的equals()里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高。
2、hashCode()既然效率这么高为什么还要equals()呢?
因为hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠,所以我们可以得出:
equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的。
hashCode()相等的两个对象他们的equals()不一定相等,也就是hashCode()不是绝对可靠的。
3、为什么要重写 hashcode 和 equals 方法?
因为原生的hashcode和equals 只是比较类似内存地址的唯一值,也就是说必须是new出的同个对象才返回相等,跟对象里的业务值没关系。 但是有的场景比如订单对象,很明显当订单号相同的两个订单对象就应该是相等的,于是我们需要重写hashcode 和equals 只判断订单号相等则对象相等。
Hashcode的位移优化
一些位运算符介绍
<< : 左移运算符,num << 1,相当于num乘以2 低位补0。
>> : 右移运算符,num >> 1,相当于num除以2 高位补0。
>>> : 无符号右移,忽略符号位,空位都以0补齐。
% : 模运算 取余。
^ : 位异或 第一个操作数的的第n位于第二个操作数的第n位相反,那么结果的第n为也为1,否则为0。
& : 与运算 第一个操作数的的第n位于第二个操作数的第n位如果都是1,那么结果的第n为也为1,否则为0。
| : 或运算 第一个操作数的的第n位于第二个操作数的第n位 只要有一个是1,那么结果的第n为也为1,否则为0。
~ : 非运算 操作数的第n位为1,那么结果的第n位为0,反之,也就是取反运算(一元操作符:只操作一个数)。
String类的hashcode方法
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
// 保证只计算一次,之后直接取hash 这个缓存
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
31*h可以被编译器优化为 h左移5位后减h , 因为31 二进制是 11111, 有较高的性能。
之所以是31原因就是,选太小的质数移位后也是区间很小,hash冲突高,如果选择很大的质数,移位后会导致区间数很大,超出int的范围,而String的hash就是用int存储的。
其它的原因我也讲不出来,还是请各位看下大佬的解释吧