LRU算法

129 阅读2分钟

LRU: Least Recently Used, 最少最近使用算法,是一种缓存淘汰算法,如手机后台软件的运行。

LRU算法的使用

const lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

从上面的操作过程,可以看出LRU需要具备以下条件:

  • 元素必须有序,以区分最近使用和久未使用的数据。容量满了之后,需要删除最久未使用的数据
  • 快速找到某个key是否存在,并得到对应的value
  • 每次访问某个key后,需要将这个元素变为最近使用的,即支持任意位置快速插入和删除元素

最终数据结构选择为:哈希链表 = 双向链表 + 哈比表

双向链表实现

// 双向链表节点
var Node = function(key, val) {
  this.key = key
  this.val = val
  this.prev = null 
  this.next = null
}

// 双向链表
var DoubleList = function() {
  // 初始化
  this.head = new Node(0, 0)
  this.tail = new Node(0, 0)
  this.head.next = this.tail
  this.tail.prev = this.head
  this.size = 0
}
// 链表尾部添加节点node
DoubleList.prototype.addLast = function(node) {
  node.prev = this.tail.prev
  node.next = this.tail
  this.tail.prev.next = node
  this.tail.prev = node
  this.size++
}
// 删除链表中的node节点
DoubleList.prototype.remove = function(node){
  node.next.prev = node.prev
  node.prev.next = node.next
  this.size--
}
// 删除链表中的第一个元素,并返回被删除的元素
DoubleList.prototype.removeFirst = function() {
  if (this.head.next === this.tail) return
  const node = this.head.next
  this.remove(node)
  return node
}
DoubleList.prototype.getSize = function() {
  return this.size
}

LRU实现

var LRUCache = function(capacity) {
  this.list = new DoubleList()
  this.capacity = capacity
  this.cache = new Map()
};
// 将某个key提升为最近使用的
// 先删除节点,再将节点插入到队尾
LRUCache.prototype.makeRecently = function(key) {
  const node = this.cache.get(key)
  this.list.remove(node)
  this.list.addLast(node)
}
// 添加最近访问的节点,并在缓存中添加节点key
LRUCache.prototype.addRecently = function(key, val) {
  const node = new Node(key, val)
  this.list.addLast(node)
  this.cache.set(key, node)
}
// 删除某个节点,并在缓存中删除节点key
LRUCache.prototype.deleteKey = function(key, val) {
  const node = this.cache.get(key)
  this.list.remove(node)
  this.cache.delete(node.key)
}
LRUCache.prototype.removeLeastRecently = function(key, val) {
  const deleteNode = this.list.removeFirst()
  this.cache.delete(deleteNode.key)
}

LRUCache.prototype.get = function(key) {
  if (!this.cache.has(key)) return -1
  this.makeRecently(key)
  return this.cache.get(key).val
};

LRUCache.prototype.put = function(key, value) {
  if (this.cache.has(key)) {
    this.deleteKey(key)
    this.addRecently(key, value)
  } else {
    if (this.capacity <= this.list.getSize()) {
      this.removeLeastRecently()
    }
    this.addRecently(key, value)
  }
};