HashMap implementation in Java. How does the bucket index calculation work?

JavaHashmap

Java Problem Overview


I am looking at the implementation of HashMap in Java and am stuck at one point.
How is the indexFor function calculated?

static int indexFor(int h, int length) {
   return h & (length-1);
}

Thanks

Java Solutions


Solution 1 - Java

The hash itself is calculated by the hashCode() method of the object you're trying to store.

What you see here is calculating the "bucket" to store the object based on the hash h. Ideally, to evade collisions, you would have the same number of buckets as is the maximum achievable value of h - but that could be too memory demanding. Therefore, you usually have a lower number of buckets with a danger of collisions.

If h is, say, 1000, but you only have 512 buckets in your underlying array, you need to know where to put the object. Usually, a mod operation on h would be enough, but that's too slow. Given the internal property of HashMap that the underlying array always has number of buckets equal to 2^n, the Sun's engineers could use the idea of h & (length-1), it does a bitwise AND with a number consisting of all 1's, practically reading only the n lowest bits of the hash (which is the same as doing h mod 2^n, only much faster).

Example:

     hash h: 11 1110 1000  -- (1000 in decimal)
   length l: 10 0000 0000  -- ( 512 in decimal)
      (l-1): 01 1111 1111  -- ( 511 in decimal - it will always be all ONEs)
h AND (l-1): 01 1110 1000  -- ( 488 in decimal which is a result of 1000 mod 512)

Solution 2 - Java

It's not calculating the hash, it's calculating the bucket.

The expression h & (length-1) does a bit-wise AND on h using length-1, which is like a bit-mask, to return only the low-order bits of h, thereby making for a super-fast variant of h % length.

Solution 3 - Java

The above answer is very good but I want to explain more why Java can use indexFor for create index

Example, I have a HashMap like this (this test is on Java7, I see Java8 change HashMap a lot but I think this logic still very good)

// Default length of "budget" (table.length) after create is 16 (HashMap#DEFAULT_INITIAL_CAPACITY)
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("A",1); // hash("A")=69, indexFor(hash,table.length)=69&(16-1) = 5
hashMap.put("B",2); // hash("B")=70, indexFor(hash,table.length)=70&(16-1) = 6
hashMap.put("P",3); // hash("P")=85, indexFor(hash,table.length)=85&(16-1) = 5
hashMap.put("A",4); // hash("A")=69, indexFor(hash,table.length)=69&(16-1) = 5
hashMap.put("r", 4);// hash("r")=117, indexFor(hash,table.length)=117&(16-1) = 5

You can see the index of entry with key "A" and object with key "P" and object with key "r" have same index (= 5). And here is the debug result after I execute code above

enter image description here

Table in the image is here

public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {
    transient HashMap.Entry<K, V>[] table;
    ...
}

=> I see
If index are different, new entry will add to table
If index is same and hash is same, new value will update
If index is same and hash is different, new entry will point to old entry (like a LinkedList). Then you know why Map.Entry have field next

static class Entry<K, V> implements java.util.Map.Entry<K, V> {
        ...
        HashMap.Entry<K, V> next;
}

You can verify it again by read the code in HashMap.

As now, you can think that HashMap will never need to change the size (16) because indexFor() always return value <= 15 but it not correct.
If you look at HashMap code

 if (this.size >= this.threshold ...) {
      this.resize(2 * this.table.length);

HashMap will resize table (double table length) when size >= threadhold

What is threadhold? threadhold is calculated below

static final int DEFAULT_INITIAL_CAPACITY = 16;
static final float DEFAULT_LOAD_FACTOR = 0.75F;
...
this.threshold = (int)Math.min((float)capacity * this.loadFactor, 1.07374182E9F); // if capacity(table.length) = 16 => threadhold = 12

What is the size? size is calculated below.
Of course, size here is not table.length .
Any time you put new entry to HashMap and HashMap need to create new entry (note that HashMap don't create new entry when the key is same, it just override new value for existed entry) then size++

void createEntry(int hash, K key, V value, int bucketIndex) {
    ...
    ++this.size;
}

Hope it help

Solution 4 - Java

It is calculating the bucket of the hash map where the entry (key-value pair) will be stored. The bucket id is hashvalue/buckets length.

A hash map consists of buckets; objects will be placed in these buckets based on the bucket id.

Any number of objects can actually fall into the same bucket based on their hash code / buckets length value. This is called a 'collision'.

If many objects fall into the same bucket, while searching their equals() method will be called to disambiguate.

The number of collisions is indirectly proportional to the bucket's length.

Solution 5 - Java

bucket_index = (i.hashCode() && 0x7FFFFFFFF) % hashmap_size does the trick

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiongnreddyView Question on Stackoverflow
Solution 1 - JavaPetr JanečekView Answer on Stackoverflow
Solution 2 - JavaBohemianView Answer on Stackoverflow
Solution 3 - JavaLinhView Answer on Stackoverflow
Solution 4 - JavaRamesh PVKView Answer on Stackoverflow
Solution 5 - JavaRitesh SinghView Answer on Stackoverflow