Libraryless. Click here for Pure Java version (8470L/47K).
1 | // size: |
2 | // 64 bytes for 0 to 1 elements |
3 | // 96 bytes for 2 to 4 elements |
4 | |
5 | /* |
6 | * #! |
7 | * Ontopia Engine |
8 | * #- |
9 | * Copyright (C) 2001 - 2013 The Ontopia Project |
10 | * #- |
11 | * Licensed under the Apache License, Version 2.0 (the "License"); |
12 | * you may not use this file except in compliance with the License. |
13 | * You may obtain a copy of the License at |
14 | * |
15 | * http://www.apache.org/licenses/LICENSE-2.0 |
16 | * |
17 | * Unless required by applicable law or agreed to in writing, software |
18 | * distributed under the License is distributed on an "AS IS" BASIS, |
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
20 | * See the License for the specific language governing permissions and |
21 | * limitations under the License. |
22 | * !# |
23 | */ |
24 | |
25 | sclass CompactHashMap<K, V> extends CompactAbstractMap<K, V> { |
26 | final static int INITIAL_SIZE = 3; |
27 | final static double LOAD_FACTOR = 0.6; |
28 | |
29 | // This object is used to represent a null KEY (null value are kept as is) |
30 | final static Object nullObject = new Object(); |
31 | |
32 | /** |
33 | * When a key is deleted this object is put into the hashtable in |
34 | * its place, so that other entries with the same key (collisions) |
35 | * further down the hashtable are not lost after we delete an object |
36 | * in the collision chain. |
37 | */ |
38 | final static Object deletedObject = new Object(); |
39 | int elements; |
40 | int freecells; |
41 | O[] table; // key, value, key, value, ... |
42 | //int modCount; |
43 | |
44 | *() { |
45 | this(INITIAL_SIZE); |
46 | } |
47 | |
48 | *(int size) { |
49 | table = new Object[(size==0 ? 1 : size)*2]; |
50 | elements = 0; |
51 | freecells = tableSize(); |
52 | //modCount = 0; |
53 | } |
54 | |
55 | // TODO: allocate smarter |
56 | *(Map<K, V> map) { |
57 | this(0); |
58 | if (map != null) putAll(map); |
59 | } |
60 | |
61 | // ===== MAP IMPLEMENTATION ============================================= |
62 | |
63 | /** |
64 | * Returns the number of key/value mappings in this map. |
65 | */ |
66 | public synchronized int size() { |
67 | return elements; |
68 | } |
69 | |
70 | /** |
71 | * Returns <tt>true</tt> if this map contains no mappings. |
72 | */ |
73 | public synchronized boolean isEmpty() { |
74 | return elements == 0; |
75 | } |
76 | |
77 | /** |
78 | * Removes all key/value mappings in the map. |
79 | */ |
80 | public synchronized void clear() { |
81 | elements = 0; |
82 | for (int ix = 0; ix < tableSize(); ix++) { |
83 | key(ix, null); |
84 | value(ix, null); |
85 | } |
86 | freecells = tableSize(); |
87 | //modCount++; |
88 | } |
89 | |
90 | /** |
91 | * Returns <tt>true</tt> if this map contains the specified key. |
92 | */ |
93 | public synchronized boolean containsKey(Object k) { |
94 | return key(findKeyIndex(k)) != null; |
95 | } |
96 | |
97 | /** |
98 | * Returns <tt>true</tt> if this map contains the specified value. |
99 | */ |
100 | public synchronized boolean containsValue(Object v) { |
101 | if (v == null) |
102 | v = (V)nullObject; |
103 | |
104 | for (int ix = 0; ix < tableSize(); ix++) |
105 | if (value(ix) != null && value(ix).equals(v)) |
106 | return true; |
107 | |
108 | return false; |
109 | } |
110 | |
111 | /** |
112 | * Returns a read-only set view of the map's keys. |
113 | */ |
114 | public synchronized Set<Entry<K, V>> entrySet() { |
115 | ret new EntrySet; |
116 | } |
117 | |
118 | /** |
119 | * Removes the mapping with key k, if there is one, and returns its |
120 | * value, if there is one, and null if there is none. |
121 | */ |
122 | public synchronized V remove(Object k) { |
123 | int index = findKeyIndex(k); |
124 | |
125 | // we found the right position, now do the removal |
126 | if (key(index) != null) { |
127 | // we found the object |
128 | |
129 | // same problem here as with put |
130 | V v = value(index); |
131 | key(index, deletedObject); |
132 | value(index, deletedObject); |
133 | //modCount++; |
134 | elements--; |
135 | return v; |
136 | } else |
137 | // we did not find the key |
138 | return null; |
139 | } |
140 | |
141 | /** |
142 | * Adds the specified mapping to this map, returning the old value for |
143 | * the mapping, if there was one. |
144 | */ |
145 | public synchronized V put(K k, V v) { |
146 | if (k == null) |
147 | k = (K)nullObject; |
148 | |
149 | int hash = k.hashCode(); |
150 | int index = (hash & 0x7FFFFFFF) % tableSize(); |
151 | int offset = 1; |
152 | int deletedix = -1; |
153 | |
154 | // search for the key (continue while !null and !this key) |
155 | while(key(index) != null && |
156 | !(key(index).hashCode() == hash && |
157 | key(index).equals(k))) { |
158 | |
159 | // if there's a deleted mapping here we can put this mapping here, |
160 | // provided it's not in here somewhere else already |
161 | if (key(index) == deletedObject) |
162 | deletedix = index; |
163 | |
164 | index = ((index + offset) & 0x7FFFFFFF) % tableSize(); |
165 | offset = offset*2 + 1; |
166 | |
167 | if (offset == -1) |
168 | offset = 2; |
169 | } |
170 | |
171 | if (key(index) == null) { // wasn't present already |
172 | if (deletedix != -1) // reusing a deleted cell |
173 | index = deletedix; |
174 | else |
175 | freecells--; |
176 | |
177 | //modCount++; |
178 | elements++; |
179 | |
180 | key(index, k); |
181 | value(index, v); |
182 | |
183 | // rehash with increased capacity |
184 | if (1 - (freecells / (double) tableSize()) > LOAD_FACTOR) |
185 | rehash(tableSize()*2 + 1); |
186 | return null; |
187 | } else { // was there already |
188 | //modCount++; |
189 | V oldv = value(index); |
190 | value(index, v); |
191 | return oldv; |
192 | } |
193 | } |
194 | |
195 | /** |
196 | * INTERNAL: Rehashes the hashmap to a bigger size. |
197 | */ |
198 | void rehash(int newCapacity) { |
199 | int oldCapacity = tableSize(); |
200 | O[] newTable = new Object[newCapacity*2]; |
201 | |
202 | for (int ix = 0; ix < oldCapacity; ix++) { |
203 | Object k = key(ix); |
204 | if (k == null || k == deletedObject) |
205 | continue; |
206 | |
207 | int hash = k.hashCode(); |
208 | int index = (hash & 0x7FFFFFFF) % newCapacity; |
209 | int offset = 1; |
210 | |
211 | // search for the key |
212 | while(newTable[index*2] != null) { // no need to test for duplicates |
213 | index = ((index + offset) & 0x7FFFFFFF) % newCapacity; |
214 | offset = offset*2 + 1; |
215 | |
216 | if (offset == -1) |
217 | offset = 2; |
218 | } |
219 | |
220 | newTable[index*2] = k; |
221 | newTable[index*2+1] = value(ix); |
222 | } |
223 | |
224 | table = newTable; |
225 | freecells = tableSize() - elements; |
226 | } |
227 | |
228 | /** |
229 | * Returns the value for the key k, if there is one, and null if |
230 | * there is none. |
231 | */ |
232 | public synchronized V get(Object k) { |
233 | return value(findKeyIndex(k)); |
234 | } |
235 | |
236 | /** |
237 | * Returns a virtual read-only collection containing all the values |
238 | * in the map. |
239 | */ |
240 | public synchronized Collection<V> values() { |
241 | return new ValueCollection(); |
242 | } |
243 | |
244 | /** |
245 | * Returns a virtual read-only set of all the keys in the map. |
246 | */ |
247 | public synchronized Set<K> keySet() { |
248 | return new KeySet(); |
249 | } |
250 | |
251 | // --- Internal utilities |
252 | |
253 | final int findKeyIndex(Object k) { |
254 | if (k == null) |
255 | k = nullObject; |
256 | |
257 | int hash = k.hashCode(); |
258 | int index = (hash & 0x7FFFFFFF) % tableSize(); |
259 | int offset = 1; |
260 | |
261 | // search for the key (continue while !null and !this key) |
262 | while(key(index) != null && |
263 | !(key(index).hashCode() == hash && |
264 | key(index).equals(k))) { |
265 | index = ((index + offset) & 0x7FFFFFFF) % tableSize(); |
266 | offset = offset*2 + 1; |
267 | |
268 | if (offset == -1) |
269 | offset = 2; |
270 | } |
271 | return index; |
272 | } |
273 | |
274 | // --- Key set |
275 | |
276 | class KeySet extends AbstractSet<K> { |
277 | public int size() { synchronized(CompactHashMap.this) { |
278 | return elements; |
279 | }} |
280 | |
281 | public boolean contains(Object k) { synchronized(CompactHashMap.this) { |
282 | return containsKey(k); |
283 | }} |
284 | |
285 | public Iterator<K> iterator() { synchronized(CompactHashMap.this) { |
286 | return new KeyIterator(); |
287 | }} |
288 | } |
289 | |
290 | class KeyIterator<K> implements Iterator<K> { |
291 | private int ix; |
292 | |
293 | private KeyIterator() { |
294 | synchronized(CompactHashMap.this) { |
295 | // walk up to first value, so that hasNext() and next() return |
296 | // correct results |
297 | for (; ix < tableSize(); ix++) |
298 | if (value(ix) != null && key(ix) != deletedObject) |
299 | break; |
300 | } |
301 | } |
302 | |
303 | public boolean hasNext() { synchronized(CompactHashMap.this) { |
304 | return ix < tableSize(); |
305 | }} |
306 | |
307 | public void remove() { |
308 | throw new UnsupportedOperationException("Collection is read-only"); |
309 | } |
310 | |
311 | public K next() { synchronized(CompactHashMap.this) { |
312 | if (ix >= tableSize()) |
313 | throw new NoSuchElementException(); |
314 | K key = (K) key(ix++); |
315 | |
316 | // walk up to next value |
317 | for (; ix < tableSize(); ix++) |
318 | if (key(ix) != null && key(ix) != deletedObject) |
319 | break; |
320 | |
321 | // ix now either points to next key, or outside array (if no next) |
322 | return key; |
323 | }} |
324 | } |
325 | |
326 | // --- Entry set |
327 | |
328 | class EntrySet extends AbstractSet<Map.Entry<K, V>> { |
329 | public int size() { synchronized(CompactHashMap.this) { |
330 | return elements; |
331 | }} |
332 | |
333 | public boolean contains(O o) { synchronized(CompactHashMap.this) { |
334 | if (o cast Map.Entry) { |
335 | O key = o.getKey(); |
336 | if (!containsKey(o)) false; |
337 | ret eq(o.getValue(), get(key)); |
338 | } |
339 | false; |
340 | }} |
341 | |
342 | public Iterator<Map.Entry<K, V>> iterator() { |
343 | return new EntryIterator(); |
344 | } |
345 | } |
346 | |
347 | class EntryIterator implements Iterator<Map.Entry<K, V>> { |
348 | private int ix; |
349 | |
350 | private EntryIterator() { |
351 | synchronized(CompactHashMap.this) { |
352 | // walk up to first value, so that hasNext() and next() return |
353 | // correct results |
354 | for (; ix < tableSize(); ix++) |
355 | if (value(ix) != null && key(ix) != deletedObject) |
356 | break; |
357 | } |
358 | } |
359 | |
360 | public boolean hasNext() { synchronized(CompactHashMap.this) { |
361 | return ix < tableSize(); |
362 | }} |
363 | |
364 | public void remove() { |
365 | throw new UnsupportedOperationException("Collection is read-only"); |
366 | } |
367 | |
368 | public Map.Entry<K, V> next() { synchronized(CompactHashMap.this) { |
369 | if (ix >= tableSize()) |
370 | throw new NoSuchElementException(); |
371 | K key = key(ix); |
372 | V val = value(ix); |
373 | ++ix; |
374 | |
375 | // walk up to next value |
376 | for (; ix < tableSize(); ix++) |
377 | if (key(ix) != null && key(ix) != deletedObject) |
378 | break; |
379 | |
380 | // ix now either points to next key, or outside array (if no next) |
381 | return simpleMapEntry(key, val); |
382 | }} |
383 | } |
384 | |
385 | // --- Value collection |
386 | |
387 | class ValueCollection<V> extends AbstractCollection<V> { |
388 | public int size() { synchronized(CompactHashMap.this) { |
389 | return elements; |
390 | }} |
391 | |
392 | public Iterator<V> iterator() { |
393 | return new ValueIterator(); |
394 | } |
395 | |
396 | public boolean contains(Object v) { |
397 | return containsValue(v); |
398 | } |
399 | } |
400 | |
401 | class ValueIterator<V> implements Iterator<V> { |
402 | private int ix; |
403 | |
404 | private ValueIterator() { |
405 | synchronized(CompactHashMap.this) { |
406 | // walk up to first value, so that hasNext() and next() return |
407 | // correct results |
408 | for (; ix < table.length/2; ix++) |
409 | if (value(ix) != null && value(ix) != deletedObject) |
410 | break; |
411 | } |
412 | } |
413 | |
414 | public boolean hasNext() { synchronized(CompactHashMap.this) { |
415 | return ix < tableSize(); |
416 | }} |
417 | |
418 | public void remove() { |
419 | throw new UnsupportedOperationException("Collection is read-only"); |
420 | } |
421 | |
422 | public V next() { synchronized(CompactHashMap.this) { |
423 | if (ix >= tableSize()) |
424 | throw new NoSuchElementException(); |
425 | V value = (V) value(ix++); |
426 | |
427 | // walk up to next value |
428 | for (; ix < tableSize(); ix++) |
429 | if (value(ix) != null && value(ix) != deletedObject) |
430 | break; |
431 | |
432 | // ix now either points to next value, or outside array (if no next) |
433 | return value; |
434 | }} |
435 | } |
436 | |
437 | K key(int i) { ret (K) table[i*2]; } |
438 | void key(int i, O key) { table[i*2] = key; } |
439 | V value(int i) { ret (V) table[i*2+1]; } |
440 | void value(int i, O value) { table[i*2+1] = value; } |
441 | |
442 | int tableSize() { ret table.length/2; } |
443 | } |
Began life as a copy of #1031038
download show line numbers debug dex old transpilations
Travelled to 9 computer(s): bhatertpkbcr, ekrmjmnbrukm, elmgxqgtpvxh, gjtlkbvenryc, mowyntqkapby, mqqgnosmbjvj, pyentgdyhuwx, vouqrxazstgt, wnsclhtenguj
No comments. add comment
Snippet ID: | #1031040 |
Snippet name: | CompactHashMap - using only one array, no modCount. synchronized |
Eternal ID of this version: | #1031040/19 |
Text MD5: | 6e79376880d0b5b9e1c2f888461c500a |
Transpilation MD5: | 55e19cdcec5a96070e6cfd14615eb19c |
Author: | stefan |
Category: | javax / collections |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2022-04-04 00:18:28 |
Source code size: | 11853 bytes / 443 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 250 / 2108 |
Version history: | 18 change(s) |
Referenced in: | [show references] |