Hash Table Fundamentals
A hash table is an enhanced array structure. While arrays provide O(1) access via integer indices, hash tables achieve similar performance using arbitrary keys (strings, numbers, etc.) through a hashing mechanism.
Implementation Approach
At the core, a hash table operates on an array where keys are converted to indices using a hash function:
public class CustomHashTable<K,V> {
private Object[] storage;
// Insert/Update operation
public void insert(K key, V value) {
int index = calculateIndex(key);
storage[index] = value;
}
// Lookup operation
public V retrieve(K key) {
int index = calculateIndex(key);
return (V) storage[index];
}
// Remove operation
public void delete(K key) {
int index = calculateIndex(key);
storage[index] = null;
}
private int calculateIndex(K key) {
// Hash calculation logic
}
}
Key Concepts and Mechanisms
Unique Keys with Duplicate Values
Each index in the underlying array is unique, but multiple values can occupy the same position through collision resolution techniques.
Hash Function Design
A hash function transforms arbitrary input into fixed-size numeric output. Java's hashCode() provides this but requires adjustments:
int h = key.hashCode();
h = h & 0x7FFFFFFF; // Clear sign bit for non-negative result
return h % storage.length; // Map to valid array index
Collisino Resolution Strategies
When different keys produce the same index:
- Chaining: Store linked lists at each array position
- Open Addressing: Sequentially probe for empty slots
Resizing and Load Factor
Collisions degrade performance (O(k) complexity). The load factor (size/capacity) determines when to resize the array. When exceeded, the table expands and rehashes all entries.
Why Iteration Order is Unreliable
Hash table iteration depends on:
- Random key distribution via hash functions
- Array resizing behavior during capacity changes
This causes iteration order to change unpredictably after modifications.
Modifying Hash Tables During Iteration
Concurrent modification while iterating can lead to:
- Unexpected array resizing
- Inconsistent traversal results
- Data visibility issues
Immutable Keys Requirement
Keys must be immutable to prevent:
- Hash code recalculation during storage
- Value lookup failures after key mutation
- Memory leaks from lost entries
Example:
// Valid immutable key
Map<String, Object> map1 = new HashMap<>();
// Problematic mutable key
Map<List<Integer>, Object> map2 = new HashMap<>(); // Risky usage
Advanced Hash Table Variants
Random Access Implementation
Adding randomKey() functionality requires auxiliary storage:
class RandomAccessMap<K,V> {
private List<K> keyList = new ArrayList<>();
private Map<K,V> valueMap = new HashMap<>();
public K getRandomKey() {
return keyList.get(new Random().nextInt(keyList.size()));
}
public void put(K key, V value) {
if (!valueMap.containsKey(key)) {
keyList.add(key);
}
valueMap.put(key, value);
}
public void remove(K key) {
// Efficient removal implementation needed
}
}
Ordered Hash Table Implementation
Maintaining insertion order using doubly linked lists:
class OrderedMap<K,V> {
private Node<K,V> head = new Node<>();
private Node<K,V> tail = new Node<>();
private Map<K,Node<K,V>> map = new HashMap<>();
public void put(K key, V value) {
if (!map.containsKey(key)) {
Node<K,V> node = new Node<>(key, value);
addLast(node);
map.put(key, node);
} else {
map.get(key).value = value;
}
}
private void addLast(Node<K,V> node) {
Node<K,V> last = tail.prev;
last.next = node;
node.prev = last;
node.next = tail;
tail.prev = node;
}
}