Tech Master Tutorials
Email Facebook Google LinkedIn Pinterest Twitter
Home Java Java 8 Java Interview Questions Java8 Interview Questions Object Oriented Programming in Java JVM Java Programming

HashMap

HashMap is one of the collection implementation classes which is a child class of the Map interface. HashMap is the data structure that is used to store the key-value pair objects.

Internal Implementation details of HashMap:

HashMap stores the key-value pairs using a hash table. In the hash table each key-value pair is mapped corresponding to the hashcode derived from the key. Using the hashing techniques a hashcode is derived from the key and using the hashcode we identify the index in the bucket list of the hashtable. This bucket list is an array of the lists of key-value pairs corresponding to the hash codes.

Following diagram depicts the clear picture how internally key-value pairs are stored. First hash code is calculated for the keys. Using the hashcode, index is identified in the bucket array. Corresponding to the hashed index, key-value pair entry is stored in the list corresponding to that particular bucket.


HashMap.png





So in HashMap, at the first level we have an array. In the array, each array index is mapped to the hashcode derived from the key. Each array index ( or we can say each bucket ) contains a reference to a linked list in which we store the key-value pair entry.

For a key-value pair, first identify the hash code for the key using the hashcode() method. Once we have the hash code, index is calculated to identify the bucket in which the key-value pair will be stored. Once we have the bucket index, check for the entry list.

If we do not have the already existing list corresponding to that bucket index then we create a list and add the key-value pair entry to the list. And if already there is a list of the key-value entries then we go through the list check for key object if the key already exist and if key already exist then we override the value. If key is not there then a new entry is created for the key-value pair. New entry is then added to the list

Important HashMap Parameters :

HashMap’s internal hash table is is created only after the first use. Initially the hash table is null. Table is initialized only when the first insertion call is made for the HashMap instance.

            transient Node<K,V>[] table;

The two parameters that affect the performance of HashMap are – initial capacity and load factor. Initial capacity is the initial size of the hash table and load factor is is a measure of how full the hash table is allowed to get before its capacity is automatically increased. Using the below Hashtable constructor, initialCapacity and loadFactor can be passed by a programmer.

	public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

Using the initial capacity and load factor, threshold is calculated. Threshold is the size limit after which table is resized/rehashed. The value of the threshold is (int)(capacity * loadFactor).)

Threshold and loadFactor fields :

    int threshold;
    final float loadFactor;
    

Deafult value for initial capacity is 16 and default load factor is 0.75.

		static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    	static final float DEFAULT_LOAD_FACTOR = 0.75f;
		


Growing the HashMap bucket :

Using the initial capacity and load factor, threshold is calculated. Threshold is the size limit after which table is resized/rehashed. The value of the threshold is (int)(capacity * loadFactor).)


HashMap Entry classes :

A Node is used as a basic bin node for entries in HashMap.
A HashMap Node is the child class of the Entry interface.

A HashMap Node(Entry) stores hash, key, value and the next node reference. Hash is the hashcode of the key. The reference points to the next node in the linkedlist of a particular bucket list. HashMap also has a TreeNode class that is a subclass of the Node class. TreeNode is used by the binary tree of a particular bucket. LinkedHashMap also has a subclass version of the HashMap Node class. To know more go to - LinkedHashMap section

Node definition:

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }



        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry e = (Map.Entry)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

TreeNode ( Addition was made as part of the Java 8 updates) :
Usually a HashMap bucket has a LinkedList for key-value pairs. But if the number of entries goes beyond a particular limit that is defined as TREEIFY_THRESHOLD, then LinkedListed is converted into a Binary Tree( a Red-Black binary Tree ). Using a binary tree decreases the lookup time for a node. As in case of linked list lookup time complexity is O(n) but in case of binary tree it will be O(log n). Same goes for the adding/updating a Node.


TreeNode Definition:

    /**
     * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn
     * extends Node) so can be used as extension of either regular or
     * linked node.
     */
    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }



Adding key-value pair to the map using put method :

public V put(K var1, V var2) {
		return this.putVal(hash(var1), var1, var2, false, true);
}

Here we pass the key - K var1 and value – V var2.

The put() method calls putVal() method internally, putVal() takes the following arguments :
• hash(var1) - hashCode of the key
• var1 – key
• var2 – value
• boolean false value
• boolean true value

putVal() first check for the HashMap table and if the table is null or empty then calls the resize() method. And resize() method creates a new table for HashMap.
Resize() method call – initially table is null. So for the first time a new table is created with the default size. If initial capacity was set using HashMap(int initialCapacity) or HashMap(int initialCapacity, float loadFactor) constructor in threshold field then initial capacity is set according to threshold.
Otherwise it picks the default initial capacity for the table size.
Default initial capacity of a HashMap is 16 which is defined using the below HashMap field.

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16


In case initial capacity is defined :
int oldThr = threshold; //Threshold is being set in oldThr local field

And then depending on the below condition, in case threshold is greater than 0, it is being set as newCap(new capacity for the table)
else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;

Otherwise using default:
newCap = DEFAULT_INITIAL_CAPACITY;

newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

New table is created using the new capacity :
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];


In other cases it checks for the MAXIMUM_CAPACITY, if current capacity is more than MAXIMUM_CAPACITY then it resize the table.

After return to putVal : Then if for index value – [(n - 1) & hash], table bucket is empty then newNode() method is called and newly create entry node is placed at the calculated index in the HashMap table.
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
Then checks for the hash value index in the hash table if there is no already existing node then it created a new Node which contains the hashCode, Key, Value and next node reference in that hash table bucket. Initially it will be null, when a new noide will be added to the same hash index, then a LinkedList will be formed as the new node with the same hashcode index will be appended next to the previously added node in a linked list manner using the next field reference.





Getting the value from the HashMap :
We can get the value for a specific key then using get method which accepts a key as parameter. Internally, get method calls getNode() method. getNode method takes the key and the hashcode of the key as parameters and returns the corresponding node. Then from the get method, corresponding value is returned.
If node is null or the stored value is null then null is returned.

    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }



   final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }





Traversing Through a HashMap :

Using EntrySet :
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;

public class TraverseMapUsingEntrySet {
	public static void main(String args[]) {
		// creating hash map
		HashMap<Integer, String> map = new HashMap<Integer, String>();

		// Adding key-value pairs to the HashMap
		map.put(1, "India");
		map.put(2, "US");
		map.put(3, "UK");
		map.put(4, "China");

		Set<Entry<Integer, String>> entrySet = map.entrySet();
		//Using entrySet we can iterate over key-value pairs using 
		//for-each construct or iterator or aggregate functions
		
		//Iterating using for-each construct
		for (Entry<Integer, String> entry : entrySet) {
			System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
		}

		System.out.println();
		
		//Traversing using iterator
		Iterator<Entry<Integer, String>> itr=entrySet.iterator();
		while (itr.hasNext()) {
			Entry<Integer,String> entry = (Entry<Integer,String>) itr.next();
			System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
		}
		
	}
}


Using KeySet :
import java.util.HashMap;
import java.util.Set;
public class TraversalUsingKeySet {
	public static void main(String args[]) {
		// creating hash map
		HashMap<Integer, String> map = new HashMap<Integer, String>();

		// Adding key-value pairs to the HashMap
		map.put(1, "India");
		map.put(2, "US");
		map.put(3, "UK");
		map.put(4, "China");

		Set<Integer> keySet = map.keySet();
		for (Integer key : keySet) {
			
			System.out.println("Key = "+key+", Value = "+map.get(key));
		}
	}
}