HashMap implementation in Java. How does the bucket index calculation work?
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
Solution 1:
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:
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:
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
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:
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.