OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 part of dart.collection; | |
6 | |
7 class _DeadEntry { | |
8 const _DeadEntry(); | |
9 } | |
10 | |
11 class _NullKey { | |
12 const _NullKey(); | |
13 int get hashCode => null.hashCode; | |
14 } | |
15 | |
16 const _TOMBSTONE = const _DeadEntry(); | |
17 const _NULL = const _NullKey(); | |
18 | |
19 class _HashTable<K> { | |
20 /** | |
21 * Table of entries with [_entrySize] slots per entry. | |
22 * | |
23 * Capacity in entries must be factor of two. | |
24 */ | |
25 List _table; | |
26 /** Current capacity. Always equal to [:_table.length ~/ _entrySize:]. */ | |
27 int _capacity; | |
28 /** Count of occupied entries, including deleted ones. */ | |
29 int _entryCount = 0; | |
30 /** Count of deleted entries. */ | |
31 int _deletedCount = 0; | |
32 /** Counter incremented when table is modified. */ | |
33 int _modificationCount = 0; | |
34 | |
35 _HashTable(int initialCapacity) : _capacity = initialCapacity { | |
36 _table = _createTable(initialCapacity); | |
37 } | |
38 | |
39 /** Reads key from table. Converts _NULL marker to null. */ | |
40 Object _key(offset) { | |
41 assert(!_isFree(_table[offset])); | |
42 Object key = _table[offset]; | |
43 if (!identical(key, _NULL)) return key; | |
44 return null; | |
45 } | |
46 | |
47 /** Writes key to table. Converts null to _NULL marker. */ | |
48 void _setKey(int offset, Object key) { | |
49 if (key == null) key = _NULL; | |
50 _table[offset] = key; | |
51 } | |
52 | |
53 int get _elementCount => _entryCount - _deletedCount; | |
54 | |
55 /** Size of each entry. */ | |
56 int get _entrySize => 1; | |
57 | |
58 void _checkModification(int expectedModificationCount) { | |
59 if (_modificationCount != expectedModificationCount) { | |
60 throw new ConcurrentModificationError(this); | |
61 } | |
62 } | |
63 | |
64 void _recordModification() { | |
65 // Value cycles after 2^30 modifications. If you keep hold of an | |
66 // iterator for that long, you might miss a modification detection, | |
67 // and iteration can go sour. Don't do that. | |
68 _modificationCount = (_modificationCount + 1) & (0x3FFFFFFF); | |
69 } | |
70 | |
71 /** | |
72 * Create an empty table. | |
73 */ | |
74 List _createTable(int capacity) { | |
75 List table = new List.fixedLength(capacity * _entrySize); | |
76 return table; | |
77 } | |
78 | |
79 /** First table probe. */ | |
80 int _firstProbe(int hashCode, int capacity) { | |
81 return hashCode & (capacity - 1); | |
82 } | |
83 | |
84 /** Following table probes. */ | |
85 int _nextProbe(int previousIndex, int probeCount, int capacity) { | |
86 return (previousIndex + probeCount) & (capacity - 1); | |
87 } | |
88 | |
89 /** Whether an object is a free-marker (either tombstone or free). */ | |
90 bool _isFree(Object marker) => | |
91 marker == null || identical(marker, _TOMBSTONE); | |
92 | |
93 /** | |
94 * Look up the offset for an object in the table. | |
95 * | |
96 * Finds the offset of the object in the table, if it is there, | |
97 * or the first free offset for its hashCode. | |
98 */ | |
99 int _probeForAdd(int hashCode, Object object) { | |
100 int entrySize = _entrySize; | |
101 int index = _firstProbe(hashCode, _capacity); | |
102 int firstTombstone = -1; | |
103 int probeCount = 0; | |
104 while (true) { | |
105 int offset = index * entrySize; | |
106 Object entry = _table[offset]; | |
107 if (identical(entry, _TOMBSTONE)) { | |
108 if (firstTombstone < 0) firstTombstone = offset; | |
109 } else if (entry == null) { | |
110 if (firstTombstone < 0) return offset; | |
111 return firstTombstone; | |
112 } else if (identical(_NULL, entry) ? _equals(null, object) | |
113 : _equals(entry, object)) { | |
114 return offset; | |
115 } | |
116 // The _nextProbe is designed so that it hits | |
117 // every index eventually. | |
118 index = _nextProbe(index, ++probeCount, _capacity); | |
119 } | |
120 } | |
121 | |
122 /** | |
123 * Look up the offset for an object in the table. | |
124 * | |
125 * If the object is in the table, its offset is returned. | |
126 * | |
127 * If the object is not in the table, Otherwise a negative value is returned. | |
128 */ | |
129 int _probeForLookup(int hashCode, Object object) { | |
130 int entrySize = _entrySize; | |
131 int index = _firstProbe(hashCode, _capacity); | |
132 int probeCount = 0; | |
133 while (true) { | |
134 int offset = index * entrySize; | |
135 Object entry = _table[offset]; | |
136 if (entry == null) { | |
137 return -1; | |
138 } else if (identical(_NULL, entry) ? _equals(null, object) | |
139 : _equals(entry, object)) { | |
140 // If entry is _TOMBSTONE, it matches nothing. | |
141 // Consider special casing it to make 'equals' calls monomorphic. | |
142 return offset; | |
143 } | |
144 // The _nextProbe is designed so that it hits | |
145 // every index eventually. | |
146 index = _nextProbe(index, ++probeCount, _capacity); | |
147 } | |
148 } | |
149 | |
150 // Override the following two to change equality/hashCode computations | |
151 | |
152 /** | |
153 * Compare two object for equality. | |
154 * | |
155 * The first object is the one already in the table, | |
156 * and the second is the one being searched for. | |
157 */ | |
158 bool _equals(Object element, Object other) { | |
159 return element == other; | |
160 } | |
161 | |
162 /** | |
163 * Compute hash-code for an object. | |
164 */ | |
165 int _hashCodeOf(Object object) => object.hashCode; | |
166 | |
167 /** | |
168 * Ensure that the table isn't too full for its own good. | |
169 * | |
170 * Call this after adding an element. | |
171 */ | |
172 int _checkCapacity() { | |
173 // Compute everything in multiples of entrySize to avoid division. | |
174 int freeCount = _capacity - _entryCount; | |
175 if (freeCount * 4 < _capacity || | |
176 freeCount < _deletedCount) { | |
177 // Less than 25% free or more deleted entries than free entries. | |
178 _grow(_entryCount - _deletedCount); | |
179 } | |
180 } | |
181 | |
182 void _grow(int contentCount) { | |
183 int capacity = _capacity; | |
184 // Don't grow to less than twice the needed capacity. | |
185 int minCapacity = contentCount * 2; | |
186 while (capacity < minCapacity) { | |
187 capacity *= 2; | |
188 } | |
189 // Reset to another table and add all existing elements. | |
190 List oldTable = _table; | |
191 _table = _createTable(capacity); | |
192 _capacity = capacity; | |
193 _entryCount = 0; | |
194 _deletedCount = 0; | |
195 _addAllEntries(oldTable); | |
196 _recordModification(); | |
197 } | |
198 | |
199 /** | |
200 * Copies all non-free entries from the old table to the new empty table. | |
201 */ | |
202 void _addAllEntries(List oldTable) { | |
203 for (int i = 0; i < oldTable.length; i += _entrySize) { | |
204 Object object = oldTable[i]; | |
205 if (!_isFree(object)) { | |
206 int toOffset = _put(object); | |
207 _copyEntry(oldTable, i, toOffset); | |
208 } | |
209 } | |
210 } | |
211 | |
212 /** | |
213 * Copies everything but the key element from one entry to another. | |
214 * | |
215 * Called while growing the base array. | |
216 * | |
217 * Override this if any non-key fields need copying. | |
218 */ | |
219 void _copyEntry(List fromTable, int fromOffset, int toOffset) {} | |
220 | |
221 // The following three methods are for simple get/set/remove operations. | |
222 // They only affect the key of an entry. The remaining fields must be | |
223 // filled by the caller. | |
224 | |
225 /** | |
226 * Returns the offset of a key in [_table], or negative if it's not there. | |
227 */ | |
228 int _get(K key) { | |
229 return _probeForLookup(_hashCodeOf(key), key); | |
230 } | |
231 | |
232 /** | |
233 * Puts the key into the table and returns its offset into [_table]. | |
234 * | |
235 * If [_entrySize] is greater than 1, the caller should fill the | |
236 * remaining fields. | |
237 * | |
238 * Remember to call [_checkCapacity] after using this method. | |
239 */ | |
240 int _put(K key) { | |
241 int offset = _probeForAdd(_hashCodeOf(key), key); | |
242 Object oldEntry = _table[offset]; | |
243 if (oldEntry == null) { | |
244 _entryCount++; | |
245 } else if (identical(oldEntry, _TOMBSTONE)) { | |
246 _deletedCount--; | |
247 } else { | |
248 return offset; | |
249 } | |
250 _setKey(offset, key); | |
251 _recordModification(); | |
252 return offset; | |
253 } | |
254 | |
255 /** | |
256 * Removes a key from the table and returns its offset into [_table]. | |
257 * | |
258 * Returns null if the key was not in the table. | |
259 * If [_entrySize] is greater than 1, the caller should clean up the | |
260 * remaining fields. | |
261 */ | |
262 int _remove(K key) { | |
263 int offset = _probeForLookup(_hashCodeOf(key), key); | |
264 if (offset >= 0) { | |
265 _deleteEntry(offset); | |
266 } | |
267 return offset; | |
268 } | |
269 | |
270 /** Clears the table completely, leaving it empty. */ | |
271 void _clear() { | |
272 if (_elementCount == 0) return; | |
273 for (int i = 0; i < _table.length; i++) { | |
274 _table[i] = null; | |
275 } | |
276 _entryCount = _deletedCount = 0; | |
277 _recordModification(); | |
278 } | |
279 | |
280 /** Clears an entry in the table. */ | |
281 void _deleteEntry(int offset) { | |
282 assert(!_isFree(_table[offset])); | |
283 _setKey(offset, _TOMBSTONE); | |
284 _deletedCount++; | |
285 _recordModification(); | |
286 } | |
287 } | |
288 | |
289 /** | |
290 * Generic iterable based on a [_HashTable]. | |
291 */ | |
292 abstract class _HashTableIterable<E> extends Iterable<E> { | |
293 final _HashTable _hashTable; | |
294 _HashTableIterable(this._hashTable); | |
295 | |
296 Iterator<E> get iterator; | |
297 | |
298 /** | |
299 * Return the iterated value for a given entry. | |
300 */ | |
301 E _valueAt(int offset, Object key); | |
302 | |
303 int get length => _hashTable._elementCount; | |
304 | |
305 /** | |
306 * Iterates over non-free entries of the table. | |
307 * | |
308 * I | |
309 */ | |
310 void forEach(void action(E element)) { | |
311 int entrySize = _hashTable._entrySize; | |
312 List table = _hashTable._table; | |
313 int modificationCount = _hashTable._modificationCount; | |
314 for (int offset = 0; offset < table.length; offset += entrySize) { | |
315 Object entry = table[offset]; | |
316 if (!_hashTable._isFree(entry)) { | |
317 E value = _valueAt(offset, entry); | |
318 action(value); | |
319 } | |
320 _hashTable._checkModification(modificationCount); | |
321 } | |
322 } | |
323 | |
324 bool get isEmpty => _hashTable._elementCount == 0; | |
325 | |
326 E get single { | |
327 if (_hashTable._elementCount > 1) { | |
328 throw new StateError("More than one element"); | |
329 } | |
330 return first; | |
331 } | |
332 } | |
333 | |
334 abstract class _HashTableIterator<E> implements Iterator<E> { | |
335 final _HashTable _hashTable; | |
336 final int _modificationCount; | |
337 /** Location right after last found element. */ | |
338 int _offset = 0; | |
339 E _current = null; | |
340 | |
341 _HashTableIterator(_HashTable hashTable) | |
342 : _hashTable = hashTable, | |
343 _modificationCount = hashTable._modificationCount; | |
344 | |
345 bool moveNext() { | |
346 _hashTable._checkModification(_modificationCount); | |
347 | |
348 List table = _hashTable._table; | |
349 int entrySize = _hashTable._entrySize; | |
350 | |
351 while (_offset < table.length) { | |
352 int currentOffset = _offset; | |
353 Object entry = table[currentOffset]; | |
354 _offset = currentOffset + entrySize; | |
355 if (!_hashTable._isFree(entry)) { | |
356 _current = _valueAt(currentOffset, entry); | |
357 return true; | |
358 } | |
359 } | |
360 _current = null; | |
361 return false; | |
362 } | |
363 | |
364 E get current => _current; | |
365 | |
366 E _valueAt(int offset, Object key); | |
367 } | |
368 | |
369 class _HashTableKeyIterable<K> extends _HashTableIterable<K> { | |
370 _HashTableKeyIterable(_HashTable<K> hashTable) : super(hashTable); | |
371 | |
372 Iterator<K> get iterator => new _HashTableKeyIterator<K>(_hashTable); | |
373 | |
374 K _valueAt(int offset, Object key) { | |
375 if (identical(key, _NULL)) return null; | |
376 return key; | |
377 } | |
378 | |
379 bool contains(Object value) => _hashTable._get(value) >= 0; | |
380 } | |
381 | |
382 class _HashTableKeyIterator<K> extends _HashTableIterator<K> { | |
383 _HashTableKeyIterator(_HashTable hashTable) : super(hashTable); | |
384 | |
385 K _valueAt(int offset, Object key) { | |
386 if (identical(key, _NULL)) return null; | |
387 return key; | |
388 } | |
389 } | |
390 | |
391 class _HashTableValueIterable<V> extends _HashTableIterable<V> { | |
392 final int _entryIndex; | |
393 | |
394 _HashTableValueIterable(_HashTable hashTable, this._entryIndex) | |
395 : super(hashTable); | |
396 | |
397 Iterator<V> get iterator { | |
398 return new _HashTableValueIterator<V>(_hashTable, _entryIndex); | |
399 } | |
400 | |
401 V _valueAt(int offset, Object key) => _hashTable._table[offset + _entryIndex]; | |
402 } | |
403 | |
404 class _HashTableValueIterator<V> extends _HashTableIterator<V> { | |
405 final int _entryIndex; | |
406 | |
407 _HashTableValueIterator(_HashTable hashTable, this._entryIndex) | |
408 : super(hashTable); | |
409 | |
410 V _valueAt(int offset, Object key) => _hashTable._table[offset + _entryIndex]; | |
411 } | |
OLD | NEW |