2 * A doubly linked list-based Least Recently Used (LRU) cache. Will keep most
\r
3 * recently used items while discarding least recently used items when its limit
\r
6 * Licensed under MIT. Copyright (c) 2010 Rasmus Andersson <http://hunch.se/>
\r
7 * See README.md for details.
\r
9 * Illustration of the design:
\r
11 * entry entry entry entry
\r
12 * ______ ______ ______ ______
\r
13 * | head |.newer => | |.newer => | |.newer => | tail |
\r
14 * | A | | B | | C | | D |
\r
15 * |______| <= older.|______| <= older.|______| <= older.|______|
\r
17 * removed <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- added
\r
19 function LRUCache (limit) {
\r
20 // Current size of the cache. (Read-only).
\r
22 // Maximum number of items this cache can hold.
\r
28 * Put <value> into the cache associated with <key>. Returns the entry which was
\r
29 * removed to make room for the new entry. Otherwise undefined is returned
\r
30 * (i.e. if there was enough room already).
\r
32 LRUCache.prototype.put = function(key, value) {
\r
33 var entry = {key:key, value:value};
\r
34 // Note: No protection agains replacing, and thus orphan entries. By design.
\r
35 this._keymap[key] = entry;
\r
37 // link previous tail to the new tail (entry)
\r
38 this.tail.newer = entry;
\r
39 entry.older = this.tail;
\r
41 // we're first in -- yay
\r
44 // add new entry to the end of the linked list -- it's now the freshest entry.
\r
46 if (this.size === this.limit) {
\r
47 // we hit the limit -- remove the head
\r
48 return this.shift();
\r
50 // increase the size counter
\r
56 * Purge the least recently used (oldest) entry from the cache. Returns the
\r
57 * removed entry or undefined if the cache was empty.
\r
59 * If you need to perform any form of finalization of purged items, this is a
\r
60 * good place to do it. Simply override/replace this function:
\r
62 * var c = new LRUCache(123);
\r
63 * c.shift = function() {
\r
64 * var entry = LRUCache.prototype.shift.call(this);
\r
65 * doSomethingWith(entry);
\r
69 LRUCache.prototype.shift = function() {
\r
70 // todo: handle special case when limit == 1
\r
71 var entry = this.head;
\r
73 if (this.head.newer) {
\r
74 this.head = this.head.newer;
\r
75 this.head.older = undefined;
\r
77 this.head = undefined;
\r
79 // Remove last strong reference to <entry> and remove links from the purged
\r
80 // entry being returned:
\r
81 entry.newer = entry.older = undefined;
\r
82 // delete is slow, but we need to do this to avoid uncontrollable growth:
\r
83 delete this._keymap[entry.key];
\r
89 * Get and register recent use of <key>. Returns the value associated with <key>
\r
90 * or undefined if not in cache.
\r
92 LRUCache.prototype.get = function(key, returnEntry) {
\r
93 // First, find our cache entry
\r
94 var entry = this._keymap[key];
\r
95 if (entry === undefined) return; // Not cached. Sorry.
\r
96 // As <key> was found in the cache, register it as being requested recently
\r
97 if (entry === this.tail) {
\r
98 // Already the most recenlty used entry, so no need to update the list
\r
101 // HEAD--------------TAIL
\r
103 // <--- add direction --
\r
106 if (entry === this.head)
\r
107 this.head = entry.newer;
\r
108 entry.newer.older = entry.older; // C <-- E.
\r
111 entry.older.newer = entry.newer; // C. --> E
\r
112 entry.newer = undefined; // D --x
\r
113 entry.older = this.tail; // D. --> E
\r
115 this.tail.newer = entry; // E. <-- D
\r
117 return returnEntry ? entry : entry.value;
\r
120 // ----------------------------------------------------------------------------
\r
121 // Following code is optional and can be removed without breaking the core
\r
125 * Check if <key> is in the cache without registering recent use. Feasible if
\r
126 * you do not want to chage the state of the cache, but only "peek" at it.
\r
127 * Returns the entry associated with <key> if found, or undefined if not found.
\r
129 LRUCache.prototype.find = function(key) {
\r
130 return this._keymap[key];
\r
134 * Update the value of entry with <key>. Returns the old value, or undefined if
\r
135 * entry was not in the cache.
\r
137 LRUCache.prototype.set = function(key, value) {
\r
138 var oldvalue, entry = this.get(key, true);
\r
140 oldvalue = entry.value;
\r
141 entry.value = value;
\r
143 oldvalue = this.put(key, value);
\r
144 if (oldvalue) oldvalue = oldvalue.value;
\r
150 * Remove entry <key> from cache and return its value. Returns undefined if not
\r
153 LRUCache.prototype.remove = function(key) {
\r
154 var entry = this._keymap[key];
\r
155 if (!entry) return;
\r
156 delete this._keymap[entry.key]; // need to do delete unfortunately
\r
157 if (entry.newer && entry.older) {
\r
158 // relink the older entry with the newer entry
\r
159 entry.older.newer = entry.newer;
\r
160 entry.newer.older = entry.older;
\r
161 } else if (entry.newer) {
\r
162 // remove the link to us
\r
163 entry.newer.older = undefined;
\r
164 // link the newer entry to head
\r
165 this.head = entry.newer;
\r
166 } else if (entry.older) {
\r
167 // remove the link to us
\r
168 entry.older.newer = undefined;
\r
169 // link the newer entry to head
\r
170 this.tail = entry.older;
\r
171 } else {// if(entry.older === undefined && entry.newer === undefined) {
\r
172 this.head = this.tail = undefined;
\r
176 return entry.value;
\r
179 /** Removes all entries */
\r
180 LRUCache.prototype.removeAll = function() {
\r
181 // This should be safe, as we never expose strong refrences to the outside
\r
182 this.head = this.tail = undefined;
\r
188 * Return an array containing all keys of entries stored in the cache object, in
\r
191 if (typeof Object.keys === 'function') {
\r
192 LRUCache.prototype.keys = function() { return Object.keys(this._keymap); }
\r
194 LRUCache.prototype.keys = function() {
\r
196 for (var k in this._keymap) keys.push(k);
\r
202 * Call `fun` for each entry. Starting with the newest entry if `desc` is a true
\r
203 * value, otherwise starts with the oldest (head) enrty and moves towards the
\r
206 * `fun` is called with 3 arguments in the context `context`:
\r
207 * `fun.call(context, Object key, Object value, LRUCache self)`
\r
209 LRUCache.prototype.forEach = function(fun, context, desc) {
\r
210 if (context === true) { desc = true; context = undefined; }
\r
211 else if (typeof context !== 'object') context = this;
\r
213 var entry = this.tail;
\r
215 fun.call(context, entry.key, entry.value, this);
\r
216 entry = entry.older;
\r
219 var entry = this.head;
\r
221 fun.call(context, entry.key, entry.value, this);
\r
222 entry = entry.newer;
\r
227 /** Returns a JSON (array) representation */
\r
228 LRUCache.prototype.toJSON = function() {
\r
229 var s = [], entry = this.head;
\r
231 s.push({key:entry.key.toJSON(), value:entry.value.toJSON()});
\r
232 entry = entry.newer;
\r
237 /** Returns a String representation */
\r
238 LRUCache.prototype.toString = function() {
\r
239 var s = '', entry = this.head;
\r
241 s += String(entry.key)+':'+entry.value;
\r
242 if (entry = entry.newer)
\r
248 // Export ourselves
\r
249 if (typeof this === 'object') this.LRUCache = LRUCache;
\r