Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Deep Dive into HashMap Implementation in Java 8

Tech Jun 9 2

Introduction

Understanding the internal workings of HashMap is essential for Java developers. This article examines the implemantation details in Java 8, focusing on the key mechanisms that govern its behavior.

Key Operations Overview

1. Resize Conditions

HashMap triggers a resize operation under the following circumstances:

  • Initialization: When the map is first created with default settings
  • Threshold exceeded: When the number of key-value pairs exceeds the threshold (capacity × load factor)
  • Tree conversion preparation: When attempting to convert a linked list to a red-black tree but the underlying array capacity is less than 64

2. Linked List to Tree Conversion

When a bucket's linked list grows sufficiently, HashMap converts it to a red-black tree for better performance. The actual conversion happens when the ninth element is added to the bucket, not the eighth as commonly believed.

Before treeification occurs, the implementation checks the array capacity:

if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
    resize();

If the array capacity is below 64, the map chooses to resize rather than convert to a tree, even if the linked list has grown long enough.

3. Tree to Linked List Conversion

The tree-to-linked list conversion is triggered during resize operations or element removal. A common misconception is that this occurs when the element count drops below 6. However, the actual implementation uses a different approach:

if (root == null || root.right == null ||
    (rl = root.left) == null || rl.left == null) {
    tab[index] = first.untreeify(map);
    return;
}

This condition checks specific structural properties of the tree rather than the element count. The conversion is based on the tree's node structure rather than a simple count threshold.

4. Resize Mechanism

When resizing occurs, the array capacity doubles. The key optimizasion is the efficient recalculation of bucket positions:

if ((e.hash & oldCap) == 0) {
    // belongs to low bucket
    if (loTail == null)
        loHead = e;
    else
        loTail.next = e;
    loTail = e;
} else {
    // belongs to high bucket
    if (hiTail == null)
        hiHead = e;
    else
        hiTail.next = e;
    hiTail = e;
}

Elements are distributed to either the original index or the index plus the old capacity based on the hash value bitwise AND with the old capacity. For linked lists, this preserves order while efficiently redistributing elements.

Implementation Analysis

Initialization

When creating a HashMap without specifying initial capacity:

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
}

The actual capacity is determined during the first insertion via the resize operation.

Put Operation

The putVal method handles several scenarios:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab;
    Node<K,V> p;
    int n, i;
    
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e;
        K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1)
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) {
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    return null;
}

Capacity Calculation

HashMap uses a bit manipulation technique to ensure proper capacity:

static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

This method rounds up the specified capacity to the next power of two.

Verification Example

The following demonstrates the tree conversion timing:

public class HashMapVerification {
    public static void main(String[] args) {
        Node head = new Node(1);
        head.next = new Node(2);
        head.next.next = new Node(3);
        head.next.next.next = new Node(4);
        head.next.next.next.next = new Node(5);
        head.next.next.next.next.next = new Node(6);
        head.next.next.next.next.next.next = new Node(7);
        
        Node current = head;
        Node nextNode;
        for (int counter = 0; ; ++counter) {
            System.out.println("Counter: " + counter);
            if ((nextNode = current.next) == null) {
                current.next = new Node(9999);
                // When counter reaches 7, current is the 8th element
                // The new element becomes the 9th element
                if (counter >= 8 - 1)
                    System.out.println("Tree conversion triggered");
                break;
            }
            current = nextNode;
        }
    }
    
    static class Node {
        public Integer value;
        public Node next;
        
        Node(Integer val) {
            this.value = val;
        }
    }
}

This confirms that tree conversion occurs when adding the ninth element to a bucket.

Tags: Java

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.