js 实现lru 缓存

83 阅读3分钟

前言

本文依赖链表实现,由于js没有自带的链表,所以先要用js实现链表,可以加强对链表数据结构的理解。

js实现链表

一个数据结构基本的功能就是增删改查,我们的链表也要实现这些功能。

首先我们来复习一下链表的数据结构。

链表通过指针将一组零散的内存块串联在一起。其中,我们把内存块称为链表的“结点”。为了将所有的结点串起来,每个链表的结点除了存储数据之外,还需要记录链上的下一个结点的地址。如图所示,我们把这个记录下个结点地址的指针叫作后继指针 next

从下图中,你应该可以发现,其中有两个结点是比较特殊的,它们分别是第一个结点和最后一个结点。我们习惯性地把第一个结点叫作头结点,把最后一个结点叫作尾结点。其中,头结点用来记录链表的基地址。有了它,我们就可以遍历得到整条链表。而尾结点特殊的地方是:指针不是指向下一个结点,而是指向一个空地址 NULL,表示这是链表上最后一个结点。

根据以上描述,我们来实现我们的基础类Node,(新建Node.js文件)

class Node {
	constructor(data) {
		this.data = data
		this.next = null
	}
}
module.exports = Node

然后新建链表类 SingList,先把用到的方法列出来,接下来实现这些方法。(新建SingList.js文件)

const Node = require('./Node')
class SingleList {
  find() {}
  findLast() {}
  findPre() {}
  append() {}
  insert() {}
  insertByHead(){}
  display() {}
  remove() {}
  getLength(){}
}

find用于查找传入的节点

	find(item) {
		let current = this.head
		while (current && current.data !== item) {
			current = current.next
		}
		return current
	}

findLast用于查找最后一个节点

	findLast() {
		let current = this.head
		while (current && current.next !== null) {
			current = current.next
		}
		return current
	}

findPre用于查找当前节点的前一个节点

	findPre(item) {
		let current = this.head
		let preNode = null
		while (current && current.next !== null) {
			if (current.next.data === item) {
				preNode = current
				break
			}
			current = current.next
		}
		return preNode
	}

append用于新增节点

	append(item) {
		let current = this.findLast()
		current.next = new Node(item)
		this.size++
	}

insert用于在某个节点之后插入节点

	insert(item, element) {
		let current = this.find(element)
		let newCur = new Node(item)
		if (!current) {
			return
		}
		let nextCur = current
		newCur.next = current.next
		nextCur.next = newCur
		this.size++
	}

insertByHead用于在哨兵节点之后插入节点

  insertByHead(item){
    let newNode=new Node(item)
    let current=this.head
    if(this.size==0){
      current.next=newNode
    }else{
      newNode.next=current.next
      current.next=newNode
    }
    this.size++
  }

display用于打印节点

	display() {
		let result = ''
		let current = this.head
		while (current && current.next !== null) {
			current = current.next
			result += current.data + '->'
		}
		console.log(result)
	}

remove移除节点

	remove(item) {
		let preNode = this.findPre(item)
		let current = this.find(item)
		if (!preNode) return null
		preNode.next = current.next
                this.size--
	}

getLength获取链表的长度

  getLength(){
    return this.size
  }

我们来测试一下我们的链表

let myList = new SingleList();
let arr = [3, 4, 5, 6, 7, 8, 9];

for(let i=0; i<arr.length; i++){
    myList.append(arr[i]);
}

myList.display();  // head->3->4->5->6->7->8->9

console.log(myList.find(4));  // Node {data: 4, prev: null, next: Node}

myList.insert(9, 9.1);
myList.insert(3, 3.1);
myList.display();  // head->3->3.1->4->5->6->7->8->9->9.1

myList.remove(9.1);
myList.remove(3);
myList.display();  // head->3.1->4->5->6->7->8->9

console.log(myList.findLast());  // Node {data: 9, prev: null, next: null}

console.log(myList.getLength());  // 7

代码测试没啥问题,接着来实现我们的LRUCache类,由于基本方法已经在链表里面实现了,我们这里只需要对链表继承,新建lru.js,

js实现LRU

LRU:最近最少使用。有两个关键点,一个是最近,一个是最少。

LRUCache

const SingleList = require('./SingleList')
// 常见的策略有三种:先进先出策略 FIFO(First In,First Out)、最少使用策略 LFU(Least Frequently Used)、最近最少使用策略 LRU(Least Recently Used)。
class LRUCache extends SingleList{
  constructor(size) {
    super();
    this.maxSize=size
  }
  add(item){
    let size=super.getLength()
    let current=super.find(item)
    if(current){
      let preNode=super.findPre(item)
      if(preNode.data!='head'){
        super.remove(item)
        super.insertByHead(item)
      }
    }else{
      if(size>=this.maxSize){
        super.remove(super.findLast().data)
      }
      super.insertByHead(item)
    }
  }
}


module.exports = LRUCache

让我们来测试一下我们的代码

const LRUCatch=require('./Lru')
let lru=new LRUCatch(3)
lru.add(1)
lru.display()  //1->
lru.add(2)
lru.display()  //2->1->
lru.add(3)
lru.display()  //3->2->1->
lru.add(1)
lru.display()  //1->3->2->
lru.add(2)
lru.display()  //2->1->3->