使用JavaScript實(shí)現(xiàn)LRU緩存的代碼詳解
引言
LRU(Least Recently Used)算法是一種廣泛應(yīng)用于內(nèi)存管理和緩存系統(tǒng)的策略,在微前端、狀態(tài)管理以及性能優(yōu)化等場(chǎng)景下,合理使用緩存機(jī)制能夠有效提升應(yīng)用性能。本文將介紹LRU算法的基本原理,并通過(guò)JavaScript實(shí)現(xiàn)案例,幫助讀者理解其在前端開(kāi)發(fā)中的應(yīng)用場(chǎng)景。
LRU算法原理
LRU(最近最少使用)算法是一種常用的緩存淘汰策略,它假定“最近最久未使用的數(shù)據(jù)在未來(lái)被訪問(wèn)的可能性最小”。當(dāng)緩存空間不足時(shí),LRU會(huì)優(yōu)先移除最近最少使用的數(shù)據(jù),為新數(shù)據(jù)騰出存儲(chǔ)空間。
實(shí)現(xiàn)方式
哈希表適合快速查找、插入和刪除的場(chǎng)景,而雙向鏈表適合頻繁插入和刪除節(jié)點(diǎn)的場(chǎng)景。在某些情況下,這兩種數(shù)據(jù)結(jié)構(gòu)也可以結(jié)合實(shí)現(xiàn)LRU緩存算法時(shí),可以使用哈希表存儲(chǔ) key 和對(duì)應(yīng)的節(jié)點(diǎn),雙向鏈表存儲(chǔ)實(shí)際的數(shù)據(jù)節(jié)點(diǎn),以實(shí)現(xiàn)快速的查找和插入刪除操作。
圖解示例
假設(shè)設(shè)計(jì)一個(gè)容量為3的LRU緩存
首先添加3個(gè)元素
哈希表中依次添加數(shù)據(jù)的key值:key1,key2,key3
雙向鏈表存儲(chǔ)哈希對(duì)(key,value),key3是最后一個(gè)添加的(最新添加記錄是key3),那么key3對(duì)應(yīng)的哈希值添加到鏈表的頭部node3。表示最近使用的。

這個(gè)時(shí)候如果要添加第四個(gè)數(shù)據(jù),key4放入哈希表,node4放入雙向鏈表頭部,node4包含key4,value4,然而此時(shí)容量不足,需要?jiǎng)h除一個(gè)元素,從尾部刪除一個(gè)最久沒(méi)使用的元素,刪除上面圖示中的node1的數(shù)據(jù),同時(shí)在哈希表中刪除node1對(duì)應(yīng)的key1 把node4添加到鏈表頭部,key4添加進(jìn)哈希表是同步進(jìn)行的。

如果最近要訪問(wèn)key3,需要把node3從當(dāng)前位置刪除,并插入到鏈表頭部

簡(jiǎn)而言之:
每次添加元素到鏈表中的時(shí)候都是從頭部添加
每次刪除元素的時(shí)候都是從尾部刪除
刪除的時(shí)候同時(shí)從哈希表里面刪除對(duì)應(yīng)的key
再次訪問(wèn)的元素,需要把元素移動(dòng)到鏈表的頭部
實(shí)現(xiàn)代碼
使用哈希鏈表,可以在每個(gè)緩存項(xiàng)的節(jié)點(diǎn)上同時(shí)存儲(chǔ)鍵值信息以及指向鏈表中前后節(jié)點(diǎn)的引用。當(dāng)一個(gè)緩存項(xiàng)被訪問(wèn)時(shí),先通過(guò)哈希表找到對(duì)應(yīng)節(jié)點(diǎn),然后將其從原有位置移出并插入鏈表尾部
class LRUCacheNode {
constructor(key, value) {
this.key = key;
this.value = value;
this.prev = null;
this.next = null;
}
}
class LRUCache {
constructor(capacity = 500) {
this.capacity = capacity;
this.cacheMap = new Map(); // 使用哈希表存儲(chǔ)鍵值對(duì)
this.doubleLinkedList = new DoublyLinkedList(); // 雙向鏈表維護(hù)緩存順序
}
get(key) {
if (this.cacheMap.has(key)) {
const node = this.cacheMap.get(key);
this.doubleLinkedList.moveToTail(node); // 將節(jié)點(diǎn)移動(dòng)到鏈表尾部,表示最新訪問(wèn)
return node.value;
}
return -1; // 或者返回null,表示key不存在于緩存中
}
put(key, value) {
if (this.cacheMap.has(key)) {
const node = this.cacheMap.get(key);
node.value = value;
this.doubleLinkedList.moveToTail(node);
} else {
if (this.cacheMap.size >= this.capacity) {
const headNode = this.doubleLinkedList.deleteHead();
this.cacheMap.delete(headNode.key); // 移除最舊的緩存項(xiàng)
}
const newNode = new Node(key, value);
this.cacheMap.set(key, newNode);
this.doubleLinkedList.addToTail(newNode);
}
}
}
// 雙向鏈表類和節(jié)點(diǎn)類的實(shí)現(xiàn)略(根據(jù)實(shí)際需求實(shí)現(xiàn))
class Node {
constructor(key, value) {
this.key = key; // 節(jié)點(diǎn)鍵值
this.value = value; // 節(jié)點(diǎn)數(shù)據(jù)值
this.prev = null; // 前驅(qū)節(jié)點(diǎn)引用
this.next = null; // 后繼節(jié)點(diǎn)引用
}
}
class DoublyLinkedList {
constructor() {
this.head = null; // 頭節(jié)點(diǎn)
this.tail = null; // 尾節(jié)點(diǎn)
}
/**
* 添加節(jié)點(diǎn)到鏈表尾部
* @param {Node} newNode 新節(jié)點(diǎn)
*/
addToTail(newNode) {
if (!this.head) {
this.head = newNode;
this.tail = newNode;
} else {
newNode.prev = this.tail;
this.tail.next = newNode;
this.tail = newNode;
}
}
/**
* 移除頭節(jié)點(diǎn)并返回
* @returns {Node | null} 刪除的頭節(jié)點(diǎn)或null(如果鏈表為空)
*/
deleteHead() {
if (!this.head) return null;
const deletedNode = this.head;
this.head = this.head.next;
if (this.head) {
this.head.prev = null;
} else {
this.tail = null;
}
return deletedNode;
}
/**
* 將指定節(jié)點(diǎn)移動(dòng)到鏈表尾部
* @param {Node} node 需要移動(dòng)的節(jié)點(diǎn)
*/
moveToTail(node) {
if (node === this.tail) return; // 如果已經(jīng)是尾節(jié)點(diǎn),則無(wú)需移動(dòng)
// 斷開(kāi)當(dāng)前節(jié)點(diǎn)與前后節(jié)點(diǎn)的連接
node.prev.next = node.next;
if (node.next) node.next.prev = node.prev;
// 將節(jié)點(diǎn)添加至鏈表尾部
this.addToTail(node);
}
// 其他可能的方法,如查找節(jié)點(diǎn)、在指定位置插入節(jié)點(diǎn)等...
}
使用場(chǎng)景
路由緩存
Vue.js 中的 keep-alive 組件雖然并未直接采用LRU算法,但在實(shí)際項(xiàng)目中,我們可以基于LRU策略自定義實(shí)現(xiàn)路由組件的緩存功能。
資源加載
對(duì)于頻繁請(qǐng)求且響應(yīng)較慢的API,可以通過(guò)LRU緩存最近請(qǐng)求的結(jié)果,減少網(wǎng)絡(luò)請(qǐng)求次數(shù)。
狀態(tài)管理
在Vuex或Redux等狀態(tài)管理庫(kù)中,也可以利用LRU算法進(jìn)行緩存,避免頻繁計(jì)算或獲取昂貴的狀態(tài)。
業(yè)務(wù)場(chǎng)景
電商大促,瀏覽器瀏覽歷史,微博熱點(diǎn)。實(shí)現(xiàn)的具體可能不同,但是思路均可使用LRU緩存實(shí)現(xiàn).
網(wǎng)頁(yè)瀏覽歷史
實(shí)現(xiàn)一個(gè)簡(jiǎn)單的瀏覽歷史記錄功能
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map();
}
get(key) {
if (this.cache.has(key)) {
const value = this.cache.get(key);
// 刪除舊數(shù)據(jù)再重新插入,以更新最近訪問(wèn)的順序
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
return null;
}
put(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
// 超出容量時(shí)刪除最久未訪問(wèn)的數(shù)據(jù)(即最近使用頻率最低的數(shù)據(jù))
const keys = this.cache.keys();
this.cache.delete(keys.next().value);
}
this.cache.set(key, value);
}
displayHistory() {
console.log("Browser History:");
for (let [key, value] of this.cache) {
console.log(key + " -> " + value);
}
}
}
// 使用示例
const historyCache = new LRUCache(5); // 設(shè)置緩存容量為5
historyCache.put("Page 1", "www.page1.com");
historyCache.put("Page 2", "www.page2.com");
historyCache.put("Page 3", "www.page3.com");
historyCache.get("Page 1");
// 輸出瀏覽歷史記錄
historyCache.displayHistory();
在上面的示例中,LRUCache 類實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的 LRU 緩存,通過(guò) get 方法獲取歷史記錄,并通過(guò) put 方法添加歷史記錄。displayHistory 方法用于展示瀏覽歷史記錄。
可以根據(jù)實(shí)際需求進(jìn)一步擴(kuò)展和優(yōu)化這個(gè)示例,比如添加時(shí)間戳來(lái)記錄訪問(wèn)時(shí)間、持久化歷史記錄到本地存儲(chǔ)等功能。希望這個(gè)示例能幫助你實(shí)現(xiàn)瀏覽歷史記錄功能。
以上就是使用JavaScript實(shí)現(xiàn)LRU緩存的代碼詳解的詳細(xì)內(nèi)容,更多關(guān)于JavaScript LRU緩存的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
.net MVC+Bootstrap下使用localResizeIMG上傳圖片
這篇文章主要為大家詳細(xì)介紹了.net MVC和Bootstrap下使用 localResizeIMG上傳圖片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
JavaScript練習(xí)小項(xiàng)目之修改div塊的顏色
這篇文章主要給大家介紹了關(guān)于JavaScript練習(xí)小項(xiàng)目之修改div塊的顏色的相關(guān)資料,文中通過(guò)舉例介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-01-01
JS新標(biāo)簽頁(yè)打開(kāi)的方法大全(讓你的網(wǎng)站訪問(wèn)更加便捷)
在開(kāi)發(fā)Web應(yīng)用中我們常常需要在當(dāng)前頁(yè)面打開(kāi)一個(gè)鏈接,但又不希望離開(kāi)當(dāng)前頁(yè)面,這篇文章主要給大家介紹了關(guān)于JS新標(biāo)簽頁(yè)打開(kāi)的方法大全,通過(guò)這些方法可以讓你的網(wǎng)站訪問(wèn)更加便捷,需要的朋友可以參考下2023-10-10
js獲得當(dāng)前時(shí)區(qū)夏令時(shí)發(fā)生和終止的時(shí)間代碼
這篇文章主要介紹了js獲得當(dāng)前時(shí)區(qū)夏令時(shí)發(fā)生和終止的時(shí)間代碼,需要的朋友可以參考下2014-02-02

