javascript - 具有大小限制和最近最少使用 (LRU) 驱逐的 JavaScript localStorage 缓存
问题描述
我正在寻找一种在浏览器中执行 Memcached 提供的方法,即配置大小限制(例如 localStorage 配额)的能力,它会自动驱逐旧项目以保持缓存在限制范围内。
好处是不需要显式删除,只要缓存键有版本/时间戳。我已经看到一些库与“项目数”限制类似,但大小限制对于保持在浏览器的配额之下会更有用。
解决方案
这无法完美实现,因为无法保证浏览器如何存储本地存储的内容,但可以创建足够接近的实现。
我们可以从 JS 中的字符串是一个16 位无符号整数值这一事实开始(因为一切都作为字符串存储在localStorage
. 这意味着我们可以很容易地获得任何字符串的大小(以字节为单位)content.length * 16 / 8
。
所以现在我们只需要创建一个缓存来跟踪存储内容的大小和键的大小,它将内容存储在下面。
一个非常原始和幼稚的实现可能是:
class Cache {
private keyStoreKey: string;
private cacheSizeKey: string;
/**
* List of the keys stored in this cache.
*/
private storedKeys: string[] = [];
/**
* The size of the stored values in bytes
*/
private cacheSize: number = 0;
constructor(
private prefix: string,
private sizeLimit: number,
) {
if(this.sizeLimit < 100) {
// the minimal size of the cache is 24 + prefix, otherwise, it would enter
// a loop trying to remove every element on any insert operation.
throw new Error('Cache size must be above 100 bytes');
}
this.keyStoreKey = `${prefix}:k`;
this.cacheSizeKey = `${prefix}:s`;
this.cacheSize = localStorage.getItem(this.cacheSizeKey) ? Number.parseInt(localStorage.getItem(this.cacheSizeKey)) : 0;
this.storedKeys = localStorage.getItem(this.keyStoreKey) ? localStorage.getItem(this.keyStoreKey).split(',') : [];
}
/**
* The size of the keys in bytes
*/
public get keyStoreSize() {
return this.calculateLenght(this.storedKeys.join(`${this.prefix}:v:,`));
}
public get totalUsedSize() {
return this.cacheSize + this.keyStoreSize + this.calculateLenght(this.keyStoreKey) + this.calculateLenght(this.cacheSizeKey);
}
/**
* Returns the size of the given string in bytes.
*
* The ECMAScript specification defines character as single 16-bit unit of UTF-16 text
*/
private calculateLenght(content: string): number {
return content.length * 16 / 8;
}
/**
* Saves an item into the cahce.
*
* NOTE: name cannot contain commas.
*/
public set(name: string, content: string) {
const newContentSize = this.calculateLenght(content);
if(!(this.storedKeys).some(storedName => storedName === name)) {
this.storedKeys.unshift(name);
this.cacheSize += newContentSize;
} else {
this.storedKeys = this.storedKeys.filter(n => n !== name);
this.storedKeys.unshift(name);
const oldContentSize = this.calculateLenght(localStorage.getItem(`${this.prefix}:v:${name}`));
this.cacheSize = this.cacheSize - oldContentSize + newContentSize;
}
while(this.totalUsedSize > this.sizeLimit && this.storedKeys.length > 0) {
this.removeOldestItem();
}
localStorage.setItem(this.cacheSizeKey, this.cacheSize.toString());
localStorage.setItem(`${this.prefix}:debug:totalSize`, this.totalUsedSize.toString());
localStorage.setItem(this.keyStoreKey, this.storedKeys.join(','));
localStorage.setItem(`${this.prefix}:v:${name}`, content);
}
/**
* The oldest item is the last in the stored keys array
*/
private removeOldestItem() {
const name = this.storedKeys.pop();
const oldContentSize = this.calculateLenght(localStorage.getItem(`${this.prefix}:v:${name}`));
this.cacheSize -= oldContentSize;
localStorage.removeItem(`${this.prefix}:v:${name}`);
}
}
window['cache'] = new Cache('test', 200);
我还没有实现读取数据的函数,但是因为键存储在一个数组中,所以您可以轻松地实现 getMostRecent() 或 getNthRecent(position) 或只是一个简单的 get(key) 函数。
如果您不熟悉它,则该实现在Typescript中,只需忽略未知部分即可。
推荐阅读
- angular - 如果动作是从另一个模块分派的,ngrx 效果不会运行
- mongodb - How to find prev/next document after sort in MongoDB
- facebook-graph-api - 存储要在多个设备上使用的数据的最佳实践?
- go - 将 Tus 可恢复文件上传协议与 Gin-Gonic CORS 问题集成
- c++ - 如何在cmake中控制shell执行的时间
- python - 具有 scipy.stats rvs() 函数的多元随机变量
- python - 如何使用 Django Rest Framework 在单个请求中反序列化多个文件和表单数据?
- javascript - 我的函数应该在 /mixins 还是 /plugins 中?
- ios - 禁用 UITableViewCell 子视图上的交互
- javascript - 如何读取在 JavaScript 中作为 Ajax 响应文本接收的数组