August 18, 2020

HashMap Implementation for Java

HashMap is a dictionary data structure provided by java. It’s a Map-based collection class that is used to store data in Key & Value pairs. In this article, we’ll be creating our own hashmap implementation.

The benefit of using this data structure is faster data retrieval. It has data access complexity of O(1) in the best case.
Image for post
In layman’s terms, a for each key we get the associated value.

To implement this structure,

  1. We need a list to store all the keys
  2. Key — Value relationship to get value based on key

We can have a list containing all the key, values and to access we need to search all of it.

But the main point of hash map is to access the keys faster in 0(1) access time.

Here, hashing comes into play. We can hash the key and relate it with the index to retrieve data faster.

Hash comes with a problem too, collision. It is always recommended to use a better hash function that can reduce chances of collision.
Image for post
Multiple hash can have same hash key. For that reason, there is a bucket or container for each key where all the values are store if collision occurs.

Let’s dive into a basic implementation of our hashmap.

Firstly, we need an array to store all the keys, a bucket model to store all the entry and a wrapper for our key, value pair.

1
2
3
4
5
6
7
8
9
10
public class MyKeyValueEntry<K, V> {
private K key;
private V value;

public MyKeyValueEntry(K key, V value) {
this.key = key;
this.value = value;
} // getters & setters
// hashCode & equals
}

Bucket to store all the key values

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyMapBucket {
private List<MyKeyValueEntry> entries;

public MyMapBucket() {
if(entries == null) {
entries = new LinkedList<>();
}
}

public List<MyKeyValueEntry> getEntries() {
return entries;
}

public void addEntry(MyKeyValueEntry entry) {
this.entries.add(entry);
}

public void removeEntry(MyKeyValueEntry entry) {
this.entries.remove(entry);
}
}

Lastly, implementation of our hashmap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class MyHashMap<K, V> {
private int CAPACITY = 10;
private MyMapBucket[] bucket;
private int size = 0;

public MyHashMap() {
this.bucket = new MyMapBucket[CAPACITY];
}
private int getHash(K key) {
return (key.hashCode() & 0xfffffff) % CAPACITY;
}

private MyKeyValueEntry getEntry(K key) {
int hash = getHash(key);
for (int i = 0; i < bucket[hash].getEntries().size(); i++) {
MyKeyValueEntry myKeyValueEntry = bucket[hash].getEntries().get(i);
if(myKeyValueEntry.getKey().equals(key)) {
return myKeyValueEntry;
}
}
return null;
} public void put(K key, V value) {
if(containsKey(key)) {
MyKeyValueEntry entry = getEntry(key);
entry.setValue(value);
} else {
int hash = getHash(key);
if(bucket[hash] == null) {
bucket[hash] = new MyMapBucket();
}
bucket[hash].addEntry(new MyKeyValueEntry<>(key, value));
size++;
}
}

public V get(K key) {
return containsKey(key) ? (V) getEntry(key).getValue() : null;
}

public boolean containsKey(K key) {
int hash = getHash(key);
return !(Objects.isNull(bucket[hash]) || Objects.isNull(getEntry(key)));
}

public void delete(K key) {
if(containsKey(key)) {
int hash = getHash(key);
bucket[hash].removeEntry(getEntry(key));
size--;
}
}
public int size() {
return size;
}
}

Put into map:

  1. If key already exists, then update value of that key.
  2. Otherwise, add an entry to the bucket.

Get from map:

  1. Check if the key exists, and return data.

Contains:

  1. Check if the bucket is null
  2. If not, then the bucket contains the key.

Performance:

It has the performance of O(1) in best case and O(n) in worst case.

Java Improvement:

  1. From Java version 8, All of the hashing based Map implementations: HashMap, Hashtable, LinkedHashMap, WeakHashMap and ConcurrentHashMap are modified to use an enhanced hashing algorithm for string keys when the capacity of the hash table has ever grown beyond 512 entries. The enhanced hashing implementation uses the murmur3 hashing algorithm along with random hash seeds and index masks. These enhancements mitigate cases where colliding String hash values could result in a performance bottleneck. Alternative String hashing implementation
  2. From Java version 8, once the number of items in a hash bucket grows beyond a certain threshold, that bucket will switch from using a linked list of entries to a balanced tree. In the case of high hash collisions, this will improve worst-case performance from O(n) to O(log n). Handle Frequent HashMap Collisions with Balanced Trees.

Java Hashmap features:

About this Post

This post is written by Mahfuzur Rahman, licensed under CC BY-NC 4.0.

#java#hashmap