Index: sdk/lib/internal/lru.dart |
diff --git a/sdk/lib/internal/lru.dart b/sdk/lib/internal/lru.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8f8a64a5fe6b7473707271041da221e87e101851 |
--- /dev/null |
+++ b/sdk/lib/internal/lru.dart |
@@ -0,0 +1,125 @@ |
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+part of dart._internal; |
+ |
+class LRUAssociation<K,V> { |
+ K key; |
+ V value; |
+ LRUAssociation previous; |
+ LRUAssociation next; |
+ |
+ void insertBetween(before, after) { |
+ after.previous = this; |
+ before.next = this; |
+ this.next = after; |
+ this.previous = before; |
+ } |
+ |
+ void remove() { |
+ var after = next; |
+ var before = previous; |
+ after.previous = before; |
+ before.next = after; |
+ } |
+} |
+ |
+/** |
+ * A map with a fixed capacity that evicts associations when capacity is reached |
+ * on a least-recently-used basis. Implemented as an open addressing hash table |
+ * with doubly-linked entries forming the LRU queue. |
+ */ |
+class LRUMap<K,V> { |
+ final LRUAssociation<K,V> _head; |
+ final List _table; |
+ final int _mask; |
+ final int _capacity; // Max number of associations before we start evicting. |
+ int _size = 0; // Current number of associations. |
+ |
+ /** |
+ * Create an LRUMap whose capacity is 75% of 2^shift. |
+ */ |
+ LRUMap.withShift(int shift) |
+ : this._mask = (1 << shift) - 1 |
+ , this._capacity = (1 << shift) * 3 ~/ 4 |
+ , this._table = new List(1 << shift) |
+ , this._head = new LRUAssociation() { |
+ // The scheme used here for handling collisions relies on there always |
+ // being at least one empty slot. |
+ if (shift < 1) throw new Exception("LRUMap requires a shift >= 1"); |
+ assert(_table.length > _capacity); |
+ _head.insertBetween(_head, _head); |
+ } |
+ |
+ int _scanFor(K key) { |
+ var start = key.hashCode & _mask; |
+ var index = start; |
+ do { |
+ var assoc = _table[index]; |
+ if (null == assoc || assoc.key == key) { |
+ return index; |
+ } |
+ index = (index + 1) & _mask; |
+ } while (index != start); |
+ // Should never happen because we start evicting associations before the |
+ // table is full. |
+ throw new Exception("Internal error: LRUMap table full"); |
+ } |
+ |
+ void _fixCollisionsAfter(start) { |
+ var assoc; |
+ var index = (start + 1) & _mask; |
+ while (null != (assoc = _table[index])) { |
+ var newIndex = _scanFor(assoc.key); |
+ if (newIndex != index) { |
+ assert(_table[newIndex] == null); |
+ _table[newIndex] = assoc; |
+ _table[index] = null; |
+ } |
+ index = (index + 1) & _mask; |
+ } |
+ } |
+ |
+ operator []=(K key, V value) { |
+ int index = _scanFor(key); |
+ var assoc = _table[index]; |
+ if (null != assoc) { |
+ // Existing key, replace value. |
+ assert(assoc.key == key); |
+ assoc.value = value; |
+ assoc.remove(); |
+ assoc.insertBetween(_head, _head.next); |
+ } else { |
+ // New key. |
+ var newAssoc; |
+ if (_size == _capacity) { |
+ // Knock out the oldest association. |
+ var lru = _head.previous; |
+ lru.remove(); |
+ index = _scanFor(lru.key); |
+ _table[index] = null; |
+ _fixCollisionsAfter(index); |
+ index = _scanFor(key); |
+ newAssoc = lru; // Recycle the association. |
+ } else { |
+ newAssoc = new LRUAssociation(); |
+ _size++; |
+ } |
+ newAssoc.key = key; |
+ newAssoc.value = value; |
+ newAssoc.insertBetween(_head, _head.next); |
+ _table[index] = newAssoc; |
+ } |
+ } |
+ |
+ V operator [](K key) { |
+ var index = _scanFor(key); |
+ var assoc = _table[index]; |
+ if (null == assoc) return null; |
+ // Move to front of LRU queue. |
+ assoc.remove(); |
+ assoc.insertBetween(_head, _head.next); |
+ return assoc.value; |
+ } |
+} |