OLD | NEW |
| (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 } | |
OLD | NEW |