Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(236)

Side by Side Diff: quiver/lib/src/collection/lru_map.dart

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « quiver/lib/src/collection/delegates/set.dart ('k') | quiver/lib/src/collection/multimap.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2014 Google Inc. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 part of quiver.collection;
16
17 /**
18 * An implementation of a [Map] which has a maximum size and uses a (Least
19 * Recently Used)[http://en.wikipedia.org/wiki/Cache_algorithms#LRU] algorithm
20 * to remove items from the [Map] when the [maximumSize] is reached and new
21 * items are added.
22 *
23 * It is safe to access the [keys] and [values] collections without affecting
24 * the "used" ordering - as well as using [forEach]. Other types of access,
25 * including bracket, and [putIfAbsent], promotes the key-value pair to the MRU
26 * position.
27 */
28 abstract class LruMap<K, V> implements Map<K, V> {
29 /**
30 * Creates a [LruMap] instance with the default implementation.
31 */
32 factory LruMap({int maximumSize}) = LinkedLruHashMap;
33
34 /**
35 * Maximum size of the [Map]. If [length] exceeds this value at any time,
36 * n entries accessed the earliest are removed, where n is [length] -
37 * [maximumSize].
38 */
39 int maximumSize;
40 }
41
42 /**
43 * Simple implementation of a linked-list entry that contains a [key] and
44 * [value].
45 */
46 class _LinkedEntry<K, V> {
47 K key;
48 V value;
49
50 _LinkedEntry<K, V> next;
51 _LinkedEntry<K, V> previous;
52
53 _LinkedEntry([this.key, this.value]);
54 }
55
56 /**
57 * A linked hash-table based implementation of [LruMap].
58 */
59 class LinkedLruHashMap<K, V> implements LruMap<K, V> {
60 static const _DEFAULT_MAXIMUM_SIZE = 100;
61
62 final Map<K, _LinkedEntry<K, V>> _entries;
63
64 int _maximumSize;
65
66 _LinkedEntry<K, V> _head;
67 _LinkedEntry<K, V> _tail;
68
69 /**
70 * Create a new LinkedLruHashMap with a [maximumSize].
71 */
72 factory LinkedLruHashMap({int maximumSize}) =>
73 new LinkedLruHashMap._fromMap(new HashMap<K, _LinkedEntry<K, V>>(),
74 maximumSize: maximumSize);
75
76 LinkedLruHashMap._fromMap(
77 this._entries, {
78 int maximumSize})
79 // This pattern is used instead of a default value because we want to
80 // be able to respect null values coming in from MapCache.lru.
81 : _maximumSize = firstNonNull(maximumSize, _DEFAULT_MAXIMUM_SIZE);
82
83 /**
84 * Adds all key-value pairs of [other] to this map.
85 *
86 * The operation is equivalent to doing this[key] = value for each key and
87 * associated value in other. It iterates over other, which must therefore not
88 * change during the iteration.
89 *
90 * If a key of [other] is already in this map, its value is overwritten. If
91 * the number of unique keys is greater than [maximumSize] then the least
92 * recently use keys are evicted. For keys written to by [other], the least
93 * recently user order is determined by [other]'s iteration order.
94 */
95 @override
96 void addAll(Map<K, V> other) => other.forEach((k, v) => this[k] = v);
97
98 @override
99 void clear() {
100 _entries.clear();
101 _head = _tail = null;
102 }
103
104 @override
105 bool containsKey(K key) => _entries.containsKey(key);
106
107 @override
108 bool containsValue(V value) => values.contains(value);
109
110 /**
111 * Applies [action] to each key-value pair of the map in order of MRU to LRU.
112 *
113 * Calling `action` must not add or remove keys from the map.
114 */
115 @override
116 void forEach(void action(K key, V value)) {
117 var head = _head;
118 while (head != null) {
119 action(head.key, head.value);
120 head = head.next;
121 }
122 }
123
124 @override
125 int get length => _entries.length;
126
127 @override
128 bool get isEmpty => _entries.isEmpty;
129
130 @override
131 bool get isNotEmpty => _entries.isNotEmpty;
132
133 /**
134 * Creates an [Iterable] around the entries of the map.
135 */
136 Iterable<_LinkedEntry<K, V>> _iterable() {
137 return new GeneratingIterable<_LinkedEntry<K, V>>(
138 () => _head, (n) => n.next);
139 }
140
141 /**
142 * The keys of [this] - in order of MRU to LRU.
143 *
144 * The returned iterable does *not* have efficient `length` or `contains`.
145 */
146 @override
147 Iterable<K> get keys => _iterable().map((e) => e.key);
148
149 /**
150 * The values of [this] - in order of MRU to LRU.
151 *
152 * The returned iterable does *not* have efficient `length` or `contains`.
153 */
154 @override
155 Iterable<V> get values => _iterable().map((e) => e.value);
156
157 @override
158 int get maximumSize => _maximumSize;
159
160 @override
161 void set maximumSize(int maximumSize) {
162 if (maximumSize == null) throw new ArgumentError.notNull('maximumSize');
163 while (length > maximumSize) {
164 _removeLru();
165 }
166 _maximumSize = maximumSize;
167 }
168
169 /**
170 * Look up the value associated with [key], or add a new value if it isn't
171 * there. The pair is promoted to the MRU position.
172 *
173 * Otherwise calls [ifAbsent] to get a new value, associates [key] to that
174 * [value], and then returns the new [value], with the key-value pair in the
175 * MRU position. If this causes [length] to exceed [maximumSize], then the
176 * LRU position is removed.
177 */
178 @override
179 V putIfAbsent(K key, V ifAbsent()) {
180 final entry = _entries.putIfAbsent(
181 key, () => _createEntry(key, ifAbsent()));
182 if (length > maximumSize) {
183 _removeLru();
184 }
185 _promoteEntry(entry);
186 return entry.value;
187 }
188
189 /**
190 * Get the value for a [key] in the [Map]. The [key] will be promoted to the
191 * 'Most Recently Used' position. *NOTE*: Calling [] inside an iteration over
192 * keys/values is currently unsupported; use [keys] or [values] if you
193 * need information about entries without modifying their position.
194 */
195 @override
196 V operator[](K key) {
197 final entry = _entries[key];
198 if (entry != null) {
199 _promoteEntry(entry);
200 return entry.value;
201 } else {
202 return null;
203 }
204 }
205
206 /**
207 * If [key] already exists, promotes it to the MRU position & assigns [value].
208 *
209 * Otherwise, adds [key] and [value] to the MRU position.
210 * If [length] exceeds [maximumSize] while adding, removes the LRU position.
211 */
212 @override
213 void operator []=(K key, V value) {
214 // Add this item to the MRU position.
215 _insertMru(_createEntry(key, value));
216
217 // Remove the LRU item if the size would be exceeded by adding this item.
218 if (length > maximumSize) {
219 assert(length == maximumSize + 1);
220 _removeLru();
221 }
222 }
223
224 @override
225 V remove(K key) {
226 final entry = _entries.remove(key);
227 if (entry != null) {
228 if (entry == _head) {
229 _head = _head.next;
230 } else if (entry == _tail) {
231 _tail.previous.next = null;
232 _tail = _tail.previous;
233 } else {
234 entry.previous.next = entry.next;
235 }
236 return entry.value;
237 }
238 return null;
239 }
240
241 @override
242 String toString() => Maps.mapToString(this);
243
244 /**
245 * Moves [entry] to the MRU position, shifting the linked list if necessary.
246 */
247 void _promoteEntry(_LinkedEntry<K, V> entry) {
248 if (entry.previous != null) {
249 // If already existed in the map, link previous to next.
250 entry.previous.next = entry.next;
251
252 // If this was the tail element, assign a new tail.
253 if (_tail == entry) {
254 _tail = entry.previous;
255 }
256 }
257
258 // Replace head with this element.
259 if (_head != null) {
260 _head.previous = entry;
261 }
262 entry.previous = null;
263 entry.next = _head;
264 _head = entry;
265
266 // Add a tail if this is the first element.
267 if (_tail == null) {
268 assert(length == 1);
269 _tail = _head;
270 }
271 }
272
273 /**
274 * Creates and returns an entry from [key] and [value].
275 */
276 _LinkedEntry<K, V> _createEntry(K key, V value) {
277 return new _LinkedEntry<K, V>(key, value);
278 }
279
280 /**
281 * If [entry] does not exist, inserts it into the backing map.
282 * If it does, replaces the existing [_LinkedEntry.value] with [entry.value].
283 * Then, in either case, promotes [entry] to the MRU position.
284 */
285 void _insertMru(_LinkedEntry<K, V> entry) {
286 // Insert a new entry if necessary (only 1 hash lookup in entire function).
287 // Otherwise, just updates the existing value.
288 final value = entry.value;
289 _promoteEntry(_entries.putIfAbsent(entry.key, () => entry)..value = value);
290 }
291
292 /**
293 * Removes the LRU position, shifting the linked list if necessary.
294 */
295 void _removeLru() {
296 // Remove the tail from the internal map.
297 _entries.remove(_tail.key);
298
299 // Remove the tail element itself.
300 _tail = _tail.previous;
301 _tail.next = null;
302 }
303 }
OLDNEW
« no previous file with comments | « quiver/lib/src/collection/delegates/set.dart ('k') | quiver/lib/src/collection/multimap.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698