网上一般都说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存储的。

其它的原因我也讲不出来,还是请各位看下大佬的解释吧