-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCustomHashMap.java
More file actions
132 lines (115 loc) · 3.73 KB
/
Copy pathCustomHashMap.java
File metadata and controls
132 lines (115 loc) · 3.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package com.sayandip.kvstore;
import java.util.ArrayList;
import java.util.List;
/**
* A hash map built from scratch: separate chaining for collision
* resolution, dynamic resizing once the load factor is exceeded.
*
* This exists to actually understand what java.util.HashMap does under
* the hood -- bucket indexing off hashCode(), collision handling via
* chaining, resize/rehash -- not to replace it. KVStore uses this
* instead of java.util.HashMap on purpose.
*/
public class CustomHashMap<K, V> {
private static final int DEFAULT_CAPACITY = 16;
private static final double LOAD_FACTOR = 0.75;
private static class Entry<K, V> {
final K key;
V value;
Entry<K, V> next; // next entry in this bucket's chain
Entry(K key, V value) {
this.key = key;
this.value = value;
}
}
private Entry<K, V>[] buckets;
private int size;
@SuppressWarnings("unchecked")
public CustomHashMap() {
buckets = new Entry[DEFAULT_CAPACITY];
size = 0;
}
private int indexFor(K key, int tableLength) {
int h = (key == null) ? 0 : key.hashCode();
// spread the high bits into the low bits so hash codes that
// only differ up top don't all collide -- same trick
// java.util.HashMap uses (h ^ (h >>> 16))
h ^= (h >>> 16);
return (tableLength - 1) & h; // tableLength is always a power of two
}
public void put(K key, V value) {
int idx = indexFor(key, buckets.length);
Entry<K, V> curr = buckets[idx];
while (curr != null) {
if (keysEqual(curr.key, key)) {
curr.value = value;
return;
}
curr = curr.next;
}
Entry<K, V> newEntry = new Entry<>(key, value);
newEntry.next = buckets[idx];
buckets[idx] = newEntry;
size++;
if ((double) size / buckets.length > LOAD_FACTOR) {
resize();
}
}
public V get(K key) {
int idx = indexFor(key, buckets.length);
Entry<K, V> curr = buckets[idx];
while (curr != null) {
if (keysEqual(curr.key, key)) return curr.value;
curr = curr.next;
}
return null;
}
public V remove(K key) {
int idx = indexFor(key, buckets.length);
Entry<K, V> curr = buckets[idx];
Entry<K, V> prev = null;
while (curr != null) {
if (keysEqual(curr.key, key)) {
if (prev == null) buckets[idx] = curr.next;
else prev.next = curr.next;
size--;
return curr.value;
}
prev = curr;
curr = curr.next;
}
return null;
}
public int size() {
return size;
}
private boolean keysEqual(K a, K b) {
return (a == null && b == null) || (a != null && a.equals(b));
}
@SuppressWarnings("unchecked")
private void resize() {
Entry<K, V>[] oldBuckets = buckets;
buckets = new Entry[oldBuckets.length * 2];
size = 0;
for (Entry<K, V> head : oldBuckets) {
Entry<K, V> curr = head;
while (curr != null) {
put(curr.key, curr.value); // rehash into the new, bigger table
curr = curr.next;
}
}
}
/** Exposed for tests/inspection only -- not part of the real API. */
int bucketCount() {
return buckets.length;
}
public List<K> keys() {
List<K> result = new ArrayList<>();
for (Entry<K, V> head : buckets) {
for (Entry<K, V> curr = head; curr != null; curr = curr.next) {
result.add(curr.key);
}
}
return result;
}
}