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