Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Java Collection Framework and HashMap Implementation Details

Tech 1

String instances are immutable: you cannot append to them directly, and any modification requires creating a new String object. StringBuilder provides mutable character sequences, supports appending operations, and automatically expands its internal buffer when capacity is exhausted by creating a larger array and copying existing data. StringBuffer is the thread-safe variant of StringBuilder.

Java compiler optimizes string concatanation with the + operator: for constant concatenation, it combines all literals into a single string at compile time. When variables are involved, the compiler uses StringBuilder to handle the concatenation, and the returned result is a heap memory address. Repeated identical constant strings will share the same memory address.

When comparing efficiency: the + operator is more efficient for a small number of string concatenations, while append() is better for larger volumes of string operations.

Java collection classes only store references to objects, not the actual object instances themselves.

The Queue interface can be used to implement both stack and queue data structures. The Set collection does not allow duplicate elements, and uses the hashCode() and equals() methods to determine if two elements are identical. Hash code collisions are possible. Common Set implementations:

  • HashSet: unordered, backed by a HashMap
  • TreeSet: maintains sorted order of elements
  • LinkedHashSet: maintains insertion order of elements

Common Map implementations enclude unordered HashMap, insertion-ordered LinkedHashMap, and sorted TreeMap.

Compare Hashtable and HashMap:

  • Thread safety: Hashtable is thread-safe, while HashMap is not
  • Null support: Hashtable does not allow null keys or values, while HashMap allows one null key and multiple null values

Compare Hashtable and ConcurrentHashMap: Collections.synchronizedMap() wraps a non-thread-safe Map into a thread-safe one by locking the entire collection object, which leads to low concurrent performance. ConcurrentHashMap from the java.util.concurrent package uses more efficient synchronization:

  • Java 7 uses segment locking to allow concurrent access to different segments
  • Java 8 replaces segment locking with per-node locking for finer-grained synchronization and better concurrency

HashMap uses an array of Node objects as its underlying storage structure, where each Node stores a key-value pair. The hash calculation for keys uses the formula (hashCode() ^ (hashCode() >>> 16)) to reduce hash collisions by mixing the higher and lower bits of the hash code.

Default configuration parameters for HashMap:

  • Default initial capacity: 16 (2^4)
  • Maximum capacity: 2^30
  • Default load factor: 0.75
  • Treeification thresholds:
    • TREEIFY_THRESHOLD = 8: convert a linked list to a red-black tree when the number of elements in a bucket exceeds 8
    • UNTREEIFY_THRESHOLD = 6: convert a tree back to a linked list when the number of elements in a bucket drops below 6
    • MIN_TREEIFY_CAPACITY = 64: the minimum table capacity required to trigger treeification; both this and the bucket size threshold must be met before treeification occurs

HashMap resizing: When the number of entries exceeds the product of the current capacity and load factor (threshold), the map doubles its capacity, creates a new underlying array, and rehashes and copies all existing entries to the new array. The initial capacity passed to the constructor is rounded up to the next power of two.

Concurrent modification during traversal: You cannot modify or delete entries in a HashMap while iterating over it, as this will throw a ConcurrentModificationException.

HashMap has both no-arg and parameterized constructors. The no-arg constructor initializes an empty underlying table with the default load factor of 0.75. Parameterized constructors accept custom initial capacity and load factor values.

putVal Method Implementation

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] hashTable; Node<K,V> currentNode; int tableSize, index;
    if ((hashTable = table) == null || (tableSize = hashTable.length) == 0) {
        tableSize = (hashTable = resize()).length;
    }
    if ((currentNode = hashTable[index = (tableSize - 1) & hash]) == null) {
        hashTable[index] = newNode(hash, key, value, null);
    } else {
        Node<K,V> existingNode; K keyRef;
        if (currentNode.hash == hash &&
            ((keyRef = currentNode.key) == key || (key != null && key.equals(keyRef)))) {
            existingNode = currentNode;
        } else if (currentNode instanceof TreeNode) {
            existingNode = ((TreeNode<K,V>)currentNode).putTreeVal(this, hashTable, hash, key, value);
        } else {
            for (int binCount = 0; ; ++binCount) {
                if ((existingNode = currentNode.next) == null) {
                    currentNode.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) {
                        treeifyBin(hashTable, hash);
                    }
                    break;
                }
                if (existingNode.hash == hash &&
                    ((keyRef = existingNode.key) == key || (key != null && key.equals(keyRef)))) {
                    break;
                }
                currentNode = existingNode;
            }
        }
        if (existingNode != null) {
            V oldValue = existingNode.value;
            if (!onlyIfAbsent || oldValue == null) {
                existingNode.value = value;
            }
            afterNodeAccess(existingNode);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold) {
        resize();
    }
    afterNodeInsertion(evict);
    return null;
}

getNode Method Implementation

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] hashTable; Node<K,V> firstBucketNode, nextNode; int tableSize; K keyRef;
    if ((hashTable = table) != null && (tableSize = hashTable.length) > 0 &&
        (firstBucketNode = hashTable[(tableSize - 1) & hash]) != null) {
        if (firstBucketNode.hash == hash &&
            ((keyRef = firstBucketNode.key) == key || (key != null && key.equals(keyRef)))) {
            return firstBucketNode;
        }
        if ((nextNode = firstBucketNode.next) != null) {
            if (firstBucketNode instanceof TreeNode) {
                return ((TreeNode<K,V>)firstBucketNode).getTreeNode(hash, key);
            }
            do {
                if (nextNode.hash == hash &&
                    ((keyRef = nextNode.key) == key || (key != null && key.equals(keyRef)))) {
                    return nextNode;
                }
            } while ((nextNode = nextNode.next) != null);
        }
    }
    return null;
}

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.