3 min read

Java Collections Framework: Lists, Maps & Trees

The Java Collections Framework (JCF) is the core architecture for storing and manipulating groups of objects. Choosing the right data structure—such as an ArrayList, LinkedList, HashMap, or ConcurrentHashMap—directly impacts memory consumption, search time complexity, and thread safety in high-throughput applications.

In this guide, we dive into data structure internals, trace HashMap bucket indexing and red-black tree treeification, evaluate ConcurrentHashMap thread safety, and compare fail-fast vs. fail-safe iterators.


1. Collections Framework Architecture

The framework separates data structures into two main root interface hierarchies: java.util.Collection (for sequences of elements) and java.util.Map (for key-value associations).


flowchart TD
    Collection["java.util.Collection"]
    Collection --> List["List\n(Ordered, Indexed, Duplicates)"]
    Collection --> Set["Set\n(Unique Elements)"]
    Collection --> Queue["Queue / Deque\n(FIFO / LIFO Ordering)"]

    List --> ArrayList["ArrayList"]
    List --> LinkedList["LinkedList"]

    Set --> HashSet["HashSet"]
    Set --> TreeSet["TreeSet"]

    Map["java.util.Map\n(Key-Value Pairs)"] --> HashMap["HashMap"]
    Map --> TreeMap["TreeMap"]
    Map --> ConcurrentHashMap["ConcurrentHashMap"]

2. List Implementations: ArrayList vs. LinkedList

ArrayList Internals

ArrayList is backed by a contiguous dynamic Object array (Object[] elementData). Accessing an element by index runs in O(1) time complexity.

  • Capacity Growth Factor: When the array exceeds its capacity, ArrayList grows by 50% (new capacity = oldCapacity + (oldCapacity >> 1)).
  • Memory Copy Cost: Growing the array requires allocating a new, larger contiguous memory array and calling System.arraycopy() to move elements, which takes O(N) time during resize operations.

LinkedList Internals

LinkedList is implemented as a doubly-linked list where each node contains pointers to prev and next nodes alongside the data element. Index access takes O(N) time because it must traverse pointers from the head or tail node.

Memory Footprint: Each LinkedList node incurs significant object header and pointer overhead (typically 24 bytes of overhead per element), making ArrayList far more cache-friendly and memory efficient for most workloads.


3. HashMap Deep Dive: Hashing & Treeification

HashMap stores key-value pairs in an array of bucket nodes (Node<K,V>[] table). The default initial capacity is 16, and the default load factor is 0.75.


flowchart LR
    subgraph BucketTable["HashMap Bucket Array (Capacity = 16)"]
        B0["Bucket 0: null"]
        B1["Bucket 1: Node (Key1) -> Node (Key2) -> Node (Key3)"]
        B2["Bucket 2: Red-Black Tree Root Node (Key8)"]
        B3["Bucket 3: null"]
    end

Bitwise Bucket Index Calculation

Rather than using expensive modulo operations (hash % capacity), HashMap enforces that array capacity is always a power of 2. It calculates the bucket index using bitwise AND:

int index = (n - 1) & hash; // Fast bitwise operation equivalent to hash % n

Collision Resolution & Red-Black Treeification

When multiple keys hash to the same bucket index (collision), HashMap chains them in a singly-linked list.

  • Treeification Threshold: When a single bucket list length reaches 8 and the total table capacity is at least 64, HashMap converts the linked list into a self-balancing Red-Black Tree.
  • Performance Impact: Treeification reduces worst-case lookup time complexity from O(N) to O(log N), protecting applications against HashDoS (Hash Denial of Service) attacks.
  • Untreeification: If deletions reduce tree nodes in a bucket to 6 or fewer, the bucket converts back into a singly-linked list.

4. ConcurrentHashMap Mechanics

Standard HashMap is not thread-safe. Concurrent modifications can cause corrupted entries or infinite loops during resizing. Hashtable and Collections.synchronizedMap() provide thread safety, but they lock the entire map on every operation, creating severe contention bottlenecks.


flowchart TD
    subgraph CHM["ConcurrentHashMap Bucket Lock Strategy"]
        Op["Insert / Update Operation"] --> CheckEmpty{"Is Bucket Empty?"}
        CheckEmpty -- Yes --> CAS["Lock-Free CAS Operation (Unsafe.compareAndSet)"]
        CheckEmpty -- No --> LockHead["Synchronize ONLY Bucket Head Node (synchronized(head))"]
    end

ConcurrentHashMap in Java 8+ replaces segment locks with fine-grained bucket-level locking:

  • CAS (Compare-And-Swap): Inserting into an empty bucket uses lock-free CAS operations (Unsafe.compareAndSetObject).
  • Bucket Synchronized Lock: Inserting into an occupied bucket locks only the head node of that specific bucket using synchronized(headNode), allowing concurrent writes across different buckets without contention.

5. Fail-Fast vs. Fail-Safe Iterators

Feature Fail-Fast Iterators Fail-Safe Iterators
Examples ArrayList, HashMap, HashSet CopyOnWriteArrayList, ConcurrentHashMap
Mechanism Checks internal modCount field during iteration. Throws exception if modified concurrently. Iterates over a clone array or a weakly-consistent view of the underlying data structure.
Exception Throws ConcurrentModificationException Never throws ConcurrentModificationException
Memory Overhead Low (Direct traversal) Higher (Cloning array or snapshot views)

Related Guides:
← PreviousJava Multithreading & Concurrency MechanicsNext →Advanced Java: Serialization, Inner Classes & Enums