Least Recently Used Cache
1. Cache 기본 개념
- 캐시는 데이터나 값을 미리 복사해 놓는 임시 장소를 가리킵니다.
- 캐시는 접근 시간에 비해 원래 데이터를 접근하는 시간이 오래 걸리는 경우나 값을 다시 계산하는 시간을 절약하고 싶은 경우 사용합니다.
- 캐시에 데이터를 미리 복사해 놓으면 계산이나 접근 시간 없이 더 빠른 속도로 데이터에 접근할 수 있습니다.
2. LRU Cache 기본 개념
- LRU는 OS의 페이지 교체 알고리즘의 하나로 최근에 가장 오랫동안 사용하지 않은 페이지를 교체하는 기법입니다.
- 캐시에 공간이 부족하면 가장 최근에 사용하지 않은 항목을 제거합니다.
3. LRU Cache 구현
- LRU Cache 구현은 Doubly Linked List를 통해 구현합니다. head에 가까운 데이터일수록 최근에 사용한 데이터이고, tail에 가까울수록 가장 오랫동안 사용하지 않은 데이터로 간주하여 새로운 데이터를 삽입할 때 가장 먼저 삭제되도록 합니다.
- 삽입된 데이터를 사용하게 되면 head로 옮겨 우선순위를 높이게 되고, 삭제될 우선순위에서 멀어지게 됩니다.
3.1 클래스 작성
class Node {
id;
data;
prevNode;
nextNode;
constructor(id, data) {
this.id = id;
this.data = data;
}
setNextNode(node) {
this.nextNode = node;
}
getNextNode(node) {
return this.nextNode;
}
setPrevNode(node) {
this.prevNode = node;
}
getPrevNode(node) {
return this.prevNode;
}
getId() {
return this.id;
}
}
class DoublyLikedList {
headNode;
tailNode;
getHeadNode() {
return this.headNode;
}
setHeadNode(headNode) {
this.headNode = headNode;
}
getTaildNode() {
return this.taildNode;
}
setTaildNode(taildNode) {
this.taildNode = taildNode;
}
}
class Constans {
static CAPACITY = 4;
}
class LRUCache {
actualSize;
map;
linkedList;
constructor() {
this.map = new Map();
this.linkedList = new DoublyLikedList();
this.actualSize = 0;
}
put(id, data) {
if (this.map.has(id)) {
const node = this.map.get(id);
node = {
...node,
data,
};
this.update(node);
return;
}
const newNode = new Node(id, data);
if (this.actualSize < Constans.CAPACITY) {
this.actualSize += 1;
this.add(newNode);
} else {
console.log('cache is full... remove tail');
this.removeTail();
this.add(newNode);
}
}
add(newNode) {
newNode.setNextNode(this.linkedList.getHeadNode());
newNode.setPrevNode(null);
if (!!this.linkedList.getHeadNode()) {
this.linkedList.getHeadNode().setPrevNode(newNode);
}
this.linkedList.setHeadNode(newNode);
if (!this.linkedList.getTaildNode()) {
this.linkedList.setTaildNode(newNode);
}
this.map.set(newNode.getId(), newNode);
}
update(node) {
const prevNode = node.getPrevNode();
const nextNode = node.getNextNode();
if (!!prevNode) {
prevNode.setNextNode(nextNode);
} else {
this.linkedList.setHeadNode(nextNode);
}
if (!!nextNode) {
nextNode.setPrevNode(prevNode);
} else {
this.linkedList.setTaildNode(prevNode);
}
this.add(node);
}
removeTail() {
this.linkedList.setTaildNode(this.linkedList.getTaildNode().getPrevNode());
if (!!this.linkedList.getTaildNode()) {
this.linkedList.getTaildNode().setNextNode(null);
}
const id = this.linkedList.getTaildNode().getId();
this.map.delete(id);
}
get(id) {
if (!this.map.has(id)) {
return null;
}
const node = this.map.get(id);
this.update(node);
return node;
}
show() {
let actualNode = this.linkedList.getHeadNode();
let text = '';
while (!!actualNode) {
text += `${actualNode.id} - ${actualNode.data} <--> `;
actualNode = actualNode.getNextNode();
}
console.log(text);
}
}
function App() {
const cache = new LRUCache();
cache.put(0, 'A');
cache.show();
cache.put(1, 'B');
cache.show();
cache.put(2, 'C');
cache.show();
cache.put(3, 'D');
cache.show();
cache.put(4, 'E');
cache.show();
cache.put(5, 'F');
cache.show();
cache.put(6, 'G');
cache.show();
console.log(cache.get(6));
cache.show();
console.log(cache.get(3));
cache.show();
console.log(cache.get(4));
cache.show();
console.log(cache.get(1));
cache.show();
}
App();
참고