Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Java Collections Internals: List Implementations and HashMap Architecture

Notes 1

The java.util.Collection interface serves as the root for all linear data structures in Java, branching into specialized subinterfaces: List for ordered sequences, Set for unique elements, Queue for FIFO operations, and Deque for double-ended access.

ArrayList Internals

ArrayList implements a dynamic array that initializes with a default capacity of ten elements. When the underlying array reaches capacity, the implementation creates a new array approximately 1.5 times the size of the current one (calculated as oldCapacity + (oldCapacity >> 1)), then copies existing elements via System.arraycopy().

LinkedList Structure

In contrast, LinkedList maintains a doubly-linked node structure where each element references both its predecessor and successor. This eliminates the need for contiguous memory allocation but introduces additional overhead per element for pointer storage.

Performance Characteristics

Index-based operations (get(int index), set(int index, E element)) execute in O(1) time for ArrayList due to direct array addressing, while LinkedList requires O(n) traversal. Conversely, insertion and deletion at arbitrary positions favor LinkedList with O(1) pointer manipulation versus ArrayList's O(n) element shifting requirement.

Concurrency Considerasions

Neither implementation provides thread-safe operations. For synchronized access, wrap instances using Collections.synchronizedList():

List<String> threadSafeVector = Collections.synchronizedList(new ArrayList<>());
List<Integer> concurrentDeque = Collections.synchronizedList(new LinkedList<>());

Note that synchronized wrappers apply method-level locking, potentially creating contention under high concurrency. For lock-free alternatives, consider CopyOnWriteArrayList or concurrent queues from java.util.concurrent.

Collection Manipulation Patterns

While java.util.Collections offers standard algorithms, modern Java provides functional approaches for common operations:

// Null-safe emptiness verification
Collection<UUID> sessionIds = new ArrayList<>();
boolean activeSessions = sessionIds != null && !sessionIds.isEmpty();

// Differential operations (set difference)
Set<String> activeUsers = new HashSet<>(Arrays.asList("alice", "bob", "charlie"));
Set<String> bannedUsers = new HashSet<>(Arrays.asList("bob"));
activeUsers.removeAll(bannedUsers);

// Intersection operations
activeUsers.retainAll(bannedUsers); // Keep only common elements

// Delimited string construction
String csvOutput = String.join(", ", activeUsers);

// Zero-length array idiom (optimal for toArray)
String[] userArray = activeUsers.toArray(new String[0]);

List Operation Examples

// Dynamic initialization with type inference
var inventory = new ArrayList<Product>();

// Append and positional insertion
inventory.add(new Product("Laptop", 999.99));
inventory.add(0, new Product("Mouse", 25.50)); // Prepends to index 0

// Random access retrieval
Product flagship = inventory.get(0);

// Iterator-based traversal
Iterator<Product> it = inventory.iterator();
while (it.hasNext()) {
    Product p = it.next();
    if (p.price() > 500) {
        it.remove(); // Safe deletion during iteration
    }
}

// Enhanced for-loop traversal
for (Product p : inventory) {
    applyDiscount(p, 0.10);
}

// Clone operations (shallow copy)
@SuppressWarnings("unchecked")
ArrayList<Product> clonedList = (ArrayList<Product>) inventory.clone();

HashMap Architecture

The HashMap implementation organizes data into an array of bins, where each bin represents a hash bucket. Prior to JDK 8, collisions resolved through linked chains; modern implementations convert chains exceeding eight entries (TREEIFY_THRESHOLD) into balanced red-black trees (reducing search complexity from O(n) to O(log n)), reverting to linked lists when sizes drop below six (UNTREEIFY_THRESHOLD).

Hash Calculation Mechanics

Determining bucket placement involves transforming the object's hash code:

  1. Compute hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16) (distributes high bits)
  2. Calculate index: (table.length - 1) & hash

This bitwise AND operation replaces modulo arithmetic (hash % length), requiring table lengths to be powers of two (default 16). For example, with a table size of 16 (binary 10000) and hash value 188 (10111100):

  10111100 (188)
& 00001111 (15, i.e., 16-1)
  --------
  00001100 (12)

Resizing Strategy

When load factor (default 0.75) multiplied by capacity exceeds the element count, the table doubles in size (32, 64, 128...). During resize, entries redistribute across new buckets based on hash & (newCapacity - 1).

Concurrent Modifications

Standard HashMap fails under concurrent modification. For thread-safe associative arrays, ConcurrentHashMap employs different strategies across JDK versions: segment-based locking in JDK 7 versus node-level CAS (Compare-And-Swap) operations combined with synchronized blocks on tree bins in JDK 8.

The CAS mechanism ensures atomicity: if the expected value matches current memory, the update succeeds; otherwise, the operation retries. This prevents lost updates without blocking threads.

HashMap Usage Patterns

// Capacity initialization with power-of-two sizing
Map<String, BigDecimal> priceMap = new HashMap<>(64);

// Insertion operations
priceMap.put("ESpresso", BigDecimal.valueOf(3.50));
priceMap.putIfAbsent("Latte", BigDecimal.valueOf(4.25));

// Retrieval with defaults
BigDecimal cost = priceMap.getOrDefault("Mocha", BigDecimal.ZERO);

// EntrySet iteration (most efficient)
for (Map.Entry<String, BigDecimal> entry : priceMap.entrySet()) {
    System.out.printf("%s: $%s%n", entry.getKey(), entry.getValue());
}

// Key existence checks
boolean hasItem = priceMap.containsKey("Cappuccino");

// Jackson JSON serialization
ObjectMapper mapper = new ObjectMapper();
String jsonPayload = mapper.writeValueAsString(priceMap);
Map<String, BigDecimal> reconstructed = mapper.readValue(
    jsonPayload, 
    new TypeReference<Map<String, BigDecimal>>() {}
);

Object Contract: equals() and hashCode()

Consistent behavior requires that equal objects (equals() returns true) produce identical hash codes. However, identical hash codes don't guarantee equality—distinct objects may collide within the same bucket. When collision occurs, HashMap traverses the chain/tree, invoking equals() to distinguish keys. Violating this contract (equal objects with different hashes) causes retrieval failures despite key presence, as the lookup targets the wrong bucket entirely.

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

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