How do I prove that Object.hashCode() can produce similar hash code for two different objects in Java?

99封情书 提交于 2019-12-05 06:23:10

2^30 unique values sounds like a lot but the birthday problem means we don't need many objects to get a collision.

The following program works for me in about a second and gives a collision between objects 196 and 121949. I suspect it will heavily depend on your system configuration, compiler version etc.

As you can see from the implementation of the Hashable class, every one is guarenteed to be unique and yet there are still collisions.

class HashCollider
{
    static class Hashable
    {
        private static int curr_id = 0;
        public  final  int id;

        Hashable()
        {
            id = curr_id++;
        }
    }

    public static void main(String[] args)
    {
        final int NUM_OBJS = 200000; // birthday problem suggests
                                     // this will be plenty

        Hashable objs[] = new Hashable[NUM_OBJS];  
        for (int i = 0; i < NUM_OBJS; ++i) objs[i] = new Hashable();

        for (int i = 0; i < NUM_OBJS; ++i)
        {
            for (int j = i + 1; j < NUM_OBJS; ++j)
            {
                if (objs[i].hashCode() == objs[j].hashCode())
                {
                    System.out.println("Objects with IDs " + objs[i].id
                                     + " and " + objs[j].id + " collided.");
                    System.exit(0);
                }
            }
        }

        System.out.println("No collision");
    }
}

If you have a large enough heap (assuming 64 bit address space) and objects are small enough (the smallest object size on a 64 bit JVM is 8 bytes), then you will be able to represent more than 2^32 objects that are reachable at the same time. At that point, the objects' identity hashcodes cannot be unique.

However, you don't need a monstrous heap. If you create a large enough pool of objects (e.g. in a large array) and randomly delete and recreate them, it is (I think) guaranteed that you will get a hashcode collision ... if you continue doing this long enough.

  • The default algorithm for hashcode in older versions of Java is based on the address of the object when hashcode is first called. If the garbage collector moves an object, and another one is created at the original address of the first one, and identityHashCode is called, then the two objects will have the same identity hashcode.

  • The current (Java 8) default algorithm uses a PRNG. The "birthday paradox" formula will tell you the probability that one object's identity hashcode is the same as one more of the other's.


The -XXhashCode=n option that @BastianJ mentioned has the following behavior:

  • hashCode == 0: Returns a freshly generated pseudo-random number

  • hashCode == 1: XORs the object address with a pseudo-random number that changes occasionally.

  • hashCode == 2: The hashCode is 1! (Hence @BastianJ's "cheat" answer.)

  • hashCode == 3: The hashcode is an ascending sequence number.

  • hashCode == 4: the bottom 32 bits of the object address

  • hashCode >= 5: This is the default algorithm for Java 8. It uses Marsaglia's xor-shift PRNG with a thread specific seed.

If you have downloaded the OpenJDK Java 8 source code, you will find the implementation in hotspot/src/share/vm/runtime/synchronizer.cp. Look for the get_next_hash() method.


So that is another way to prove it. Show him the source code!

Use Oracle JVM and set -XX:hashCode=2. If I remember corretly, this chooses the Default implementation to be "constant 1". Just for the purpose of proving you're right.

Stuart Marks

I have little to add to Michael's answer (+1) except a bit of code golfing and statistics.

The Wikipedia article on the Birthday problem that Michael linked to has a nice table of the number of events necessary to get a collision, with a desired probability, given a value space of a particular size. For example, Java's hashCode has 32 bits, giving a value space of 4 billion. To get a collision with a probability of 50%, about 77,000 events are necessary.

Here's a simple way to find two instances of Object that have the same hashCode:

static int findCollision() {
    Map<Integer,Object> map = new HashMap<>();
    Object n, o;

    do {
        n = new Object();
        o = map.put(n.hashCode(), n);
    } while (o == null);

    assert n != o && n.hashCode() == o.hashCode();
    return map.size() + 1;
}

This returns the number of attempts it took to get a collision. I ran this a bunch of times and generated some statistics:

    System.out.println(
        IntStream.generate(HashCollisions::findCollision)
                 .limit(1000)
                 .summaryStatistics());

IntSummaryStatistics{count=1000, sum=59023718, min=635, average=59023.718000, max=167347}

This seems quite in line with the numbers from the Wikipedia table. Incidentally, this took only about 10 seconds to run on my laptop, so this is far from a pathological case.

You were right in the first place, but it bears repeating: hash codes are not unique!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!