20
20
import java .util .Iterator ;
21
21
import java .util .NoSuchElementException ;
22
22
23
- /** An unordered map that uses identity comparison for keys. This implementation is a cuckoo hash map using 3 hashes, random
24
- * walking, and a small stash for problematic keys. Null keys are not allowed. Null values are allowed. No allocation is done
25
- * except when growing the table size. <br>
23
+ /** An unordered map that uses identity comparison for keys. This implementation is a cuckoo hash map using 3 hashes
24
+ * (if table size is less than 2^16) or 4 hashes (if table size is greater than or equal to 2^16), random walking, and
25
+ * a small stash for problematic keys Null keys are not allowed. Null values are allowed. No allocation is done except when
26
+ * growing the table size. <br>
26
27
* <br>
27
28
* This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower,
28
29
* depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the
29
30
* next higher POT size.
30
31
* @author Nathan Sweet */
31
32
public class IdentityMap <K , V > {
32
- private static final int PRIME1 = 0xbe1f14b1 ;
33
- private static final int PRIME2 = 0xb4b82e39 ;
34
- private static final int PRIME3 = 0xced1c241 ;
33
+ // primes for hash functions 2, 3, and 4
34
+ private static final int PRIME2 = 0xbe1f14b1 ;
35
+ private static final int PRIME3 = 0xb4b82e39 ;
36
+ private static final int PRIME4 = 0xced1c241 ;
35
37
36
38
public int size ;
37
39
@@ -43,6 +45,7 @@ public class IdentityMap<K, V> {
43
45
private int hashShift , mask , threshold ;
44
46
private int stashCapacity ;
45
47
private int pushIterations ;
48
+ private boolean isBigTable ;
46
49
47
50
private Entries entries ;
48
51
private Values values ;
@@ -70,6 +73,9 @@ public IdentityMap (int initialCapacity, float loadFactor) {
70
73
if (loadFactor <= 0 ) throw new IllegalArgumentException ("loadFactor must be > 0: " + loadFactor );
71
74
this .loadFactor = loadFactor ;
72
75
76
+ // big table is when capacity >= 2^16
77
+ isBigTable = (capacity >>> 16 ) != 0 ? true : false ;
78
+
73
79
threshold = (int )(capacity * loadFactor );
74
80
mask = capacity - 1 ;
75
81
hashShift = 31 - Integer .numberOfTrailingZeros (capacity );
@@ -82,7 +88,10 @@ public IdentityMap (int initialCapacity, float loadFactor) {
82
88
83
89
public V put (K key , V value ) {
84
90
if (key == null ) throw new IllegalArgumentException ("key cannot be null." );
91
+ // avoid getfield opcode
85
92
K [] keyTable = this .keyTable ;
93
+ int mask = this .mask ;
94
+ boolean isBigTable = this .isBigTable ;
86
95
87
96
// Check for existing keys.
88
97
int hashCode = System .identityHashCode (key );
@@ -110,6 +119,18 @@ public V put (K key, V value) {
110
119
return oldValue ;
111
120
}
112
121
122
+ int index4 = -1 ;
123
+ K key4 = null ;
124
+ if (isBigTable ) {
125
+ index4 = hash4 (hashCode );
126
+ key4 = keyTable [index4 ];
127
+ if (key4 == key ) {
128
+ V oldValue = valueTable [index4 ];
129
+ valueTable [index4 ] = value ;
130
+ return oldValue ;
131
+ }
132
+ }
133
+
113
134
// Update key in the stash.
114
135
for (int i = capacity , n = i + stashSize ; i < n ; i ++) {
115
136
if (keyTable [i ] == key ) {
@@ -141,7 +162,14 @@ public V put (K key, V value) {
141
162
return null ;
142
163
}
143
164
144
- push (key , value , index1 , key1 , index2 , key2 , index3 , key3 );
165
+ if (isBigTable && key4 == null ) {
166
+ keyTable [index4 ] = key ;
167
+ valueTable [index4 ] = value ;
168
+ if (size ++ >= threshold ) resize (capacity << 1 );
169
+ return null ;
170
+ }
171
+
172
+ push (key , value , index1 , key1 , index2 , key2 , index3 , key3 , index4 , key4 );
145
173
return null ;
146
174
}
147
175
@@ -176,21 +204,37 @@ private void putResize (K key, V value) {
176
204
return ;
177
205
}
178
206
179
- push (key , value , index1 , key1 , index2 , key2 , index3 , key3 );
207
+ int index4 = -1 ;
208
+ K key4 = null ;
209
+ if (isBigTable ) {
210
+ index4 = hash4 (hashCode );
211
+ key4 = keyTable [index4 ];
212
+ if (key4 == null ) {
213
+ keyTable [index4 ] = key ;
214
+ valueTable [index4 ] = value ;
215
+ if (size ++ >= threshold ) resize (capacity << 1 );
216
+ return ;
217
+ }
218
+ }
219
+
220
+ push (key , value , index1 , key1 , index2 , key2 , index3 , key3 , index4 , key4 );
180
221
}
181
222
182
- private void push (K insertKey , V insertValue , int index1 , K key1 , int index2 , K key2 , int index3 , K key3 ) {
223
+ private void push (K insertKey , V insertValue , int index1 , K key1 , int index2 , K key2 , int index3 , K key3 , int index4 , K key4 ) {
224
+ // avoid getfield opcode
183
225
K [] keyTable = this .keyTable ;
184
226
V [] valueTable = this .valueTable ;
185
227
int mask = this .mask ;
228
+ boolean isBigTable = this .isBigTable ;
186
229
187
230
// Push keys until an empty bucket is found.
188
231
K evictedKey ;
189
232
V evictedValue ;
190
233
int i = 0 , pushIterations = this .pushIterations ;
234
+ int n = isBigTable ? 4 : 3 ;
191
235
do {
192
236
// Replace the key and value for one of the hashes.
193
- switch (ObjectMap .random .nextInt (3 )) {
237
+ switch (ObjectMap .random .nextInt (n )) {
194
238
case 0 :
195
239
evictedKey = key1 ;
196
240
evictedValue = valueTable [index1 ];
@@ -203,12 +247,18 @@ private void push (K insertKey, V insertValue, int index1, K key1, int index2, K
203
247
keyTable [index2 ] = insertKey ;
204
248
valueTable [index2 ] = insertValue ;
205
249
break ;
206
- default :
250
+ case 2 :
207
251
evictedKey = key3 ;
208
252
evictedValue = valueTable [index3 ];
209
253
keyTable [index3 ] = insertKey ;
210
254
valueTable [index3 ] = insertValue ;
211
255
break ;
256
+ default :
257
+ evictedKey = key4 ;
258
+ evictedValue = valueTable [index4 ];
259
+ keyTable [index4 ] = insertKey ;
260
+ valueTable [index4 ] = insertValue ;
261
+ break ;
212
262
}
213
263
214
264
// If the evicted key hashes to an empty bucket, put it there and stop.
@@ -240,6 +290,17 @@ private void push (K insertKey, V insertValue, int index1, K key1, int index2, K
240
290
return ;
241
291
}
242
292
293
+ if (isBigTable ) {
294
+ index4 = hash4 (hashCode );
295
+ key4 = keyTable [index4 ];
296
+ if (key4 == null ) {
297
+ keyTable [index4 ] = evictedKey ;
298
+ valueTable [index4 ] = evictedValue ;
299
+ if (size ++ >= threshold ) resize (capacity << 1 );
300
+ return ;
301
+ }
302
+ }
303
+
243
304
if (++i == pushIterations ) break ;
244
305
245
306
insertKey = evictedKey ;
@@ -271,7 +332,15 @@ public V get (K key) {
271
332
index = hash2 (hashCode );
272
333
if (key != keyTable [index ]) {
273
334
index = hash3 (hashCode );
274
- if (key != keyTable [index ]) return getStash (key , null );
335
+ if (key != keyTable [index ]) {
336
+ if (isBigTable ) {
337
+ index = hash4 (hashCode );
338
+ if (key != keyTable [index ]) return getStash (key , null );
339
+ }
340
+ else {
341
+ return getStash (key , null );
342
+ }
343
+ }
275
344
}
276
345
}
277
346
return valueTable [index ];
@@ -284,7 +353,15 @@ public V get (K key, V defaultValue) {
284
353
index = hash2 (hashCode );
285
354
if (key != keyTable [index ]) {
286
355
index = hash3 (hashCode );
287
- if (key != keyTable [index ]) return getStash (key , defaultValue );
356
+ if (key != keyTable [index ]) {
357
+ if (isBigTable ) {
358
+ index = hash4 (hashCode );
359
+ if (key != keyTable [index ]) return getStash (key , defaultValue );
360
+ }
361
+ else {
362
+ return getStash (key , defaultValue );
363
+ }
364
+ }
288
365
}
289
366
}
290
367
return valueTable [index ];
@@ -326,6 +403,17 @@ public V remove (K key) {
326
403
return oldValue ;
327
404
}
328
405
406
+ if (isBigTable ) {
407
+ index = hash4 (hashCode );
408
+ if (keyTable [index ] == key ) {
409
+ keyTable [index ] = null ;
410
+ V oldValue = valueTable [index ];
411
+ valueTable [index ] = null ;
412
+ size --;
413
+ return oldValue ;
414
+ }
415
+ }
416
+
329
417
return removeStash (key );
330
418
}
331
419
@@ -412,7 +500,14 @@ public boolean containsKey (K key) {
412
500
index = hash2 (hashCode );
413
501
if (key != keyTable [index ]) {
414
502
index = hash3 (hashCode );
415
- if (key != keyTable [index ]) return containsKeyStash (key );
503
+ if (key != keyTable [index ]) {
504
+ if (isBigTable ) {
505
+ index = hash4 (hashCode );
506
+ if (key != keyTable [index ]) return containsKeyStash (key );
507
+ } else {
508
+ return containsKeyStash (key );
509
+ }
510
+ }
416
511
}
417
512
}
418
513
return true ;
@@ -462,6 +557,9 @@ private void resize (int newSize) {
462
557
stashCapacity = Math .max (3 , (int )Math .ceil (Math .log (newSize )) * 2 );
463
558
pushIterations = Math .max (Math .min (newSize , 8 ), (int )Math .sqrt (newSize ) / 8 );
464
559
560
+ // big table is when capacity >= 2^16
561
+ isBigTable = (capacity >>> 16 ) != 0 ? true : false ;
562
+
465
563
K [] oldKeyTable = keyTable ;
466
564
V [] oldValueTable = valueTable ;
467
565
@@ -489,6 +587,11 @@ private int hash3 (int h) {
489
587
return (h ^ h >>> hashShift ) & mask ;
490
588
}
491
589
590
+ private int hash4 (int h ) {
591
+ h *= PRIME4 ;
592
+ return (h ^ h >>> hashShift ) & mask ;
593
+ }
594
+
492
595
public String toString () {
493
596
if (size == 0 ) return "[]" ;
494
597
StringBuilder buffer = new StringBuilder (32 );
0 commit comments