NOTE

设计LRU缓存结构

设计 LRU 缓存结构:map + 双向链表,以及 Go / Java 实现。

Data Structures & Algorithms创建于 更新于 约 1 分钟读完historical

这是历史学习笔记,可能存在过时或不完整的理解。

1. 题目描述

设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能

  • set(key, value):将记录(key, value)插入该结构
  • get(key):返回key对应的value值

2. 思路

  • map+双向链表

3. 实现

3.1. Golang

3.1.1. map+list

package main

import (
	"container/list"
)

/**
 * lru design
 * @param operators int整型二维数组 the ops
 * @param k int整型 the k
 * @return int整型一维数组
 */

type data struct {
	key   int
	value int
}

type LruCache struct {
	eList    *list.List
	eMap     map[int]*list.Element
	capacity int
}

func NewLruCache(capacity int) *LruCache {
	return &LruCache{
		eList:    list.New(),
		eMap:     make(map[int]*list.Element, 0),
		capacity: capacity,
	}
}

func (l *LruCache) Set(key int, value int) {

	//已存在但不是头节点
	element, ok := l.eMap[key]
	if ok {
		element.Value.(*data).value = value
		l.eList.MoveToFront(element)
		return
	}

	//不存在并且已满
	if l.isFull() {
		lastElement := l.eList.Back()
		//删除尾节点
		l.eList.Remove(lastElement)
		//清理map
		delete(l.eMap, lastElement.Value.(*data).key)
	}

	//不存在并且未满
	element = l.eList.PushFront(&data{
		key:   key,
		value: value,
	})
	l.eMap[key] = element
}

func (l *LruCache) isFull() bool {
	return len(l.eMap) == l.capacity
}

func (l *LruCache) Get(key int) int {
	element, ok := l.eMap[key]
	if !ok {
		return -1
	}

	l.eList.MoveToFront(element)
	return element.Value.(*data).value
}

func LRU(operators [][]int, k int) []int {

	res := make([]int, 0)
	cache := NewLruCache(k)
	for _, operator := range operators {
		switch operator[0] {
		case 1:
			cache.Set(operator[1], operator[2])
		case 2:
			val := cache.Get(operator[1])
			res = append(res, val)
		}
	}

	return res
}

3.1.2. map+slice

type node struct {
	key   int
	value int
}

type LRUCache struct {
	m        map[int]*node
	lst      []*node
	capacity int
}

func Constructor(capacity int) LRUCache {
	return LRUCache{
		m:        make(map[int]*node, 0),
		lst:      make([]*node, 0),
		capacity: capacity,
	}
}

func (this *LRUCache) Get(key int) int {
	n, ok := this.m[key]
	if !ok {
		return -1
	}

	// 存在那么把node移动到链表头部
	this.moveToHead(key)
	return n.value
}

func (this *LRUCache) moveToHead(key int) {
	var movedNode *node
	var movedIndex int
	for i, n := range this.lst {
		if n.key == key {
			movedNode = n
			movedIndex = i
			break
		}
	}

	newList := append([]*node{movedNode}, this.lst[:movedIndex]...)
	newList = append(newList, this.lst[movedIndex+1:]...)
	this.lst = newList
}

func (this *LRUCache) Put(key int, value int) {
	n, ok := this.m[key]
	if ok {
		n.value = value
		// 存在那么把node移动到链表头部
		this.moveToHead(key)
		return
	}

	if len(this.m) == this.capacity {
		// 取出链表尾部并删除
		tail := this.removeTail()
		// 删除m中的元素
		delete(this.m, tail.key)
	}

	// 新建node
	newNode := &node{
		key:   key,
		value: value,
	}
	// 插入链表头部
	this.addToHead(newNode)
	// 插入map
	this.m[key] = newNode
}

func (this *LRUCache) addToHead(n *node) {
	this.lst = append([]*node{n}, this.lst...)
}

func (this *LRUCache) removeTail() *node {
	tail := this.lst[len(this.lst)-1]
	this.lst = this.lst[:len(this.lst)-1]
	return tail
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * obj := Constructor(capacity);
 * param_1 := obj.Get(key);
 * obj.Put(key,value);
 */
package main

import "fmt"


type node struct {
    key int
    value int
}

type LRU struct {
    m map[int]*node
    lst []*node
    capacity int
}

func (l *LRU)String() string {
    s := ""
    for _, n := range l.lst {
        s += fmt.Sprintf("%d:%d,", n.key,n.value)
    }
    
    return s
} 

func LRUCache(capacity int ) *LRU {
    return &LRU{
        m: make(map[int]*node),
        lst: make([]*node, 0),
        capacity:capacity,
    }
}


func (l *LRU) get(key int) int {
    n, ok := l.m[key]
    if !ok {
        return -1
    }
    
    idx := findNodeFromList(l.lst, n)
    newLst := []*node{l.lst[idx]}
    newLst = append(newLst, l.lst[:idx]...)
    newLst = append(newLst, l.lst[idx+1:]...)
    l.lst = newLst
    return n.value
}

func findNodeFromList(lst []*node, n *node) int {
    for idx,nd := range lst {
        if nd == n {
            return idx
        }
    }
    return 0
}


func (l *LRU) put(key, value int) {
    n, ok := l.m[key]
    if ok {
        n.value = value
        idx := findNodeFromList(l.lst, n)
        newLst := []*node{l.lst[idx]}
        newLst = append(newLst, l.lst[:idx]...)
        newLst = append(newLst, l.lst[idx+1:]...)
        l.lst = newLst
        return
    }
    

    
    if l.capacity == len(l.m) { 
        deleteNode := l.lst[len(l.lst)-1]
        l.lst = l.lst[:len(l.lst)-1]
        delete(l.m, deleteNode.key)
    }
    
    addNode := &node{
        key: key,
        value: value,
    }
    l.m[key] = addNode
    newLst := make([]*node, 0)
    newLst = append(newLst, addNode)
    newLst = append(newLst, l.lst...)
    l.lst = newLst
}



func main() {
    lru := LRUCache(3)
    
    fmt.Println(lru.get(1))
    lru.put(1,10)
    fmt.Println(lru.get(1))
    
    lru.put(2,20)
    lru.put(3,30)
    fmt.Println(lru)
    
    lru.put(4,40)
    fmt.Println(lru)
    
    lru.put(4,50)
    fmt.Println(lru)
}

3.2. Java

public class LRU
{
    public static void main(String[] args)
    {
        //默认LinkedHashMap是按照插入顺序排序的,可以用有参的构造函数设置成按访问顺序排序
        Map<Integer,Integer> map = new LinkedHashMap<Integer, Integer>(3, (float) 0.75, true)
        {
            //重写removeEldestEntry方法,设置LRU的容量
            //size大于3的时候如果在插入新的数据,那么会把链表头部的节点删除
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest)
            {
                return size() > 3;
            }
        };

        map.put(1, 1);
        map.put(2, 2);
        map.put(3, 3);
        System.out.println(map);//{1=1, 2=2, 3=3}
        map.put(4, 4);
        System.out.println(map);//{2=2, 3=3, 4=4},删除了头部的1,尾部插入了4
        map.get(2);
        System.out.println(map);//{3=3, 4=4, 2=2},2移动到了尾部

    }
}

4. 参考

讨论

使用 GitHub 账号参与讨论,评论会保存在 GitHub Issues 中。在 GitHub 查看