Sobes.tech
Middle

How is the HashMap data structure organized and structured in Java?

sobes.tech AI

Answer from AI

HashMap in Java is based on hashing principles. It stores key-value pairs.

Internal structure:

  • Array of buckets (containers). Each bucket is a linked list (or a tree, starting from Java 8, when there are many collisions).
  • When adding an element (put):
    1. The hash code of the key (key.hashCode()) is computed.
    2. The hash code is modified for better distribution (hash).
    3. Using the modified hash and the size of the bucket array, the index of the bucket where the element will be placed is calculated (hash & (array_size - 1)).
    4. The element (a "key-value" pair as a Node object) is placed into this bucket. If the bucket already contains elements, the new element is added at the beginning of the linked list or tree.
    5. When adding, it checks for the presence of the key: the equals() method is used to compare keys inside the bucket. If the key is found, the value is updated.
  • When retrieving an element (get):
    1. The bucket index is calculated by the key.
    2. Inside the bucket, the element is searched by key using hashCode() and equals() methods.
    3. The associated value is returned.

Organization:

  • Collisions: If multiple keys have the same hash code and fall into the same bucket, the elements are stored as a linked list. From Java 8, when the number of elements in a bucket exceeds a threshold (usually 8), the linked list is converted into a tree for faster search (O(log n) instead of O(n)).
  • Resizing: When the number of elements exceeds the "load factor" (load factor * capacity), HashMap increases the size of the internal bucket array (usually doubles) and rehashes all elements. This is an expensive operation (O(n)).
  • Parameters:
    • capacity: initial size of the bucket array (default 16).
    • load factor: load threshold (default 0.75). It determines when resizing occurs.

Why hashCode() and equals() are important:

  • Proper functioning of HashMap depends on correct implementation of these methods.
  • If equals() returns true for two objects, then hashCode() must return the same value.
  • Incorrect implementation can lead to elements not being found (get returns null) even if they were added.

Example of Node structure:

// Internal only
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;
    }
}

HashMap provides fast (average O(1)) addition, retrieval, and removal of elements with uniform hash distribution. In the worst case (heavy collisions), the operation can become O(n) or O(log n) with trees.

Not thread-safe. For thread-safe use, ConcurrentHashMap or Collections.synchronizedMap(new HashMap<...>(...)) should be used.