package.structs.LRUCache.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ol Show documentation
Show all versions of ol Show documentation
OpenLayers mapping library
The newest version!
/**
* @module ol/structs/LRUCache
*/
import Disposable from '../Disposable.js';
import {assert} from '../asserts.js';
/**
* @typedef {Object} Entry
* @property {string} key_ Key.
* @property {Entry|null} newer Newer.
* @property {Entry|null} older Older.
* @property {*} value_ Value.
*/
/**
* @classdesc
* Implements a Least-Recently-Used cache where the keys do not conflict with
* Object's properties (e.g. 'hasOwnProperty' is not allowed as a key). Expiring
* items from the cache is the responsibility of the user.
*
* @fires import("../events/Event.js").default
* @template T
*/
class LRUCache {
/**
* @param {number} [highWaterMark] High water mark.
*/
constructor(highWaterMark) {
/**
* Desired max cache size after expireCache(). If set to 0, no cache entries
* will be pruned at all.
* @type {number}
*/
this.highWaterMark = highWaterMark !== undefined ? highWaterMark : 2048;
/**
* @private
* @type {number}
*/
this.count_ = 0;
/**
* @private
* @type {!Object}
*/
this.entries_ = {};
/**
* @private
* @type {?Entry}
*/
this.oldest_ = null;
/**
* @private
* @type {?Entry}
*/
this.newest_ = null;
}
/**
* @return {boolean} Can expire cache.
*/
canExpireCache() {
return this.highWaterMark > 0 && this.getCount() > this.highWaterMark;
}
/**
* Expire the cache. When the cache entry is a {@link module:ol/Disposable~Disposable},
* the entry will be disposed.
* @param {!Object} [keep] Keys to keep. To be implemented by subclasses.
*/
expireCache(keep) {
while (this.canExpireCache()) {
const entry = this.pop();
if (entry instanceof Disposable) {
entry.dispose();
}
}
}
/**
* FIXME empty description for jsdoc
*/
clear() {
this.count_ = 0;
this.entries_ = {};
this.oldest_ = null;
this.newest_ = null;
}
/**
* @param {string} key Key.
* @return {boolean} Contains key.
*/
containsKey(key) {
return this.entries_.hasOwnProperty(key);
}
/**
* @param {function(T, string, LRUCache): ?} f The function
* to call for every entry from the oldest to the newer. This function takes
* 3 arguments (the entry value, the entry key and the LRUCache object).
* The return value is ignored.
*/
forEach(f) {
let entry = this.oldest_;
while (entry) {
f(entry.value_, entry.key_, this);
entry = entry.newer;
}
}
/**
* @param {string} key Key.
* @param {*} [options] Options (reserved for subclasses).
* @return {T} Value.
*/
get(key, options) {
const entry = this.entries_[key];
assert(
entry !== undefined,
'Tried to get a value for a key that does not exist in the cache',
);
if (entry === this.newest_) {
return entry.value_;
}
if (entry === this.oldest_) {
this.oldest_ = /** @type {Entry} */ (this.oldest_.newer);
this.oldest_.older = null;
} else {
entry.newer.older = entry.older;
entry.older.newer = entry.newer;
}
entry.newer = null;
entry.older = this.newest_;
this.newest_.newer = entry;
this.newest_ = entry;
return entry.value_;
}
/**
* Remove an entry from the cache.
* @param {string} key The entry key.
* @return {T} The removed entry.
*/
remove(key) {
const entry = this.entries_[key];
assert(
entry !== undefined,
'Tried to get a value for a key that does not exist in the cache',
);
if (entry === this.newest_) {
this.newest_ = /** @type {Entry} */ (entry.older);
if (this.newest_) {
this.newest_.newer = null;
}
} else if (entry === this.oldest_) {
this.oldest_ = /** @type {Entry} */ (entry.newer);
if (this.oldest_) {
this.oldest_.older = null;
}
} else {
entry.newer.older = entry.older;
entry.older.newer = entry.newer;
}
delete this.entries_[key];
--this.count_;
return entry.value_;
}
/**
* @return {number} Count.
*/
getCount() {
return this.count_;
}
/**
* @return {Array} Keys.
*/
getKeys() {
const keys = new Array(this.count_);
let i = 0;
let entry;
for (entry = this.newest_; entry; entry = entry.older) {
keys[i++] = entry.key_;
}
return keys;
}
/**
* @return {Array} Values.
*/
getValues() {
const values = new Array(this.count_);
let i = 0;
let entry;
for (entry = this.newest_; entry; entry = entry.older) {
values[i++] = entry.value_;
}
return values;
}
/**
* @return {T} Last value.
*/
peekLast() {
return this.oldest_.value_;
}
/**
* @return {string} Last key.
*/
peekLastKey() {
return this.oldest_.key_;
}
/**
* Get the key of the newest item in the cache. Throws if the cache is empty.
* @return {string} The newest key.
*/
peekFirstKey() {
return this.newest_.key_;
}
/**
* Return an entry without updating least recently used time.
* @param {string} key Key.
* @return {T|undefined} Value.
*/
peek(key) {
return this.entries_[key]?.value_;
}
/**
* @return {T} value Value.
*/
pop() {
const entry = this.oldest_;
delete this.entries_[entry.key_];
if (entry.newer) {
entry.newer.older = null;
}
this.oldest_ = /** @type {Entry} */ (entry.newer);
if (!this.oldest_) {
this.newest_ = null;
}
--this.count_;
return entry.value_;
}
/**
* @param {string} key Key.
* @param {T} value Value.
*/
replace(key, value) {
this.get(key); // update `newest_`
this.entries_[key].value_ = value;
}
/**
* @param {string} key Key.
* @param {T} value Value.
*/
set(key, value) {
assert(
!(key in this.entries_),
'Tried to set a value for a key that is used already',
);
const entry = {
key_: key,
newer: null,
older: this.newest_,
value_: value,
};
if (!this.newest_) {
this.oldest_ = entry;
} else {
this.newest_.newer = entry;
}
this.newest_ = entry;
this.entries_[key] = entry;
++this.count_;
}
/**
* Set a maximum number of entries for the cache.
* @param {number} size Cache size.
* @api
*/
setSize(size) {
this.highWaterMark = size;
}
}
export default LRUCache;