关于LRU缓存
- LRU - Lease Recently Used 最近使用
- 如果内存优先,只缓存最近使用的,删除 ‘沉睡’ 数据
- 核心 api: get set
Typescript 版算法实现
1 )方案1
- 分析
- 使用哈希表来实现, O(1)
- 必须是有序的,常用放在前面,沉睡放在后面, 即:有序,可排序
- 这样 {} 不符合要求;Map是可以排序的,按照设置顺序
class LRUCache {
private length: number
private data: Map<any, any> = new Map()
constructor(length: number) {
if (length < 1) throw new Error('invalid length')
this.length = length
}
set(key: any, value: any) {
const data = this.data
if (data.has(key)) {
data.delete(key)
}
data.set(key, value)
if (data.size > this.length) {
// 如果超出了容量,则删除 Map 最老的元素
const delKey = data.keys().next().value
data.delete(delKey)
}
}
get(key: any): any {
const data = this.data
if(!data.has(key)) return null
const value = data.get(key)
data.delete(key)
data.set(key, value) // 保持最新的
return value
}
}
const lruCache = new LRUCache(2)
lruCache.set(1, 1) // { 1=1 }
lruCache.set(2, 2) // { 1=1, 2=2 }
console.log(lruCache.get(1)) // 1 {2=2, 1=1}
lruCache.set(3,3) // {1=1, 3=3}
console.log(lruCache.get(2)) // null
lruCache.set(4,4) // {3=3, 4=4}
console.log(lruCache.get(1)) // null
console.log(lruCache.get(3)) // 3 {4=4, 3=3}
console.log(lruCache.get(4)) // 4 {3=3, 4=4}
2 ) 方案2
- 不用 Map 如何实现 LRU 缓存
- 注意:有序
- 结合 Object + Array
- 将Array改造成双向链表
const obj1 = {value: 1, key: 'a'}
const obj2 = {value: 2, key: 'b'}
const obj3 = {value: 3, key: 'c'}
const data = [obj1, obj2, obj3]
const map = {'a': obj1, 'b': obj2, 'c': obj3}
上述数据结构很精妙,如此,给出了下面的新方案
interface IListNode {
value: any
key: string
prev?: IListNode
next?: IListNode
}
class LRUCache2 {
private length: number
private data: { [key: string]: IListNode } = {}
private dataLength: number = 0
private listHead: IListNode | null = null
private listTail: IListNode | null = null
constructor(length: number) {
if (length < 1) throw new Error('invalid length')
this.length = length
}
private moveToTail(curNode: IListNode) {
const tail = this.listTail
if (tail === curNode) return
// 1. 让 pre next 断绝与 curNode的关系
const preNode = curNode.prev
const nextNode = curNode.next
if (preNode) {
if (nextNode) {
preNode.next = nextNode
} else {
delete preNode.next
}
}
if (nextNode) {
if (preNode) {
nextNode.prev = preNode
} else {
delete nextNode.prev
}
if (this.listHead === curNode) this.listHead = nextNode
}
// 2. 让 curNode 断绝与 prev, next的关系
delete curNode.prev
delete curNode.next
// 3. 在list 末尾重新建立 curNode 的新关系
if (tail) {
tail.next = curNode
curNode.prev = tail
}
this.listTail = curNode
}
private tryClean() {
while(this.dataLength > this.length) {
const head = this.listHead
if (!head) throw new Error('head is null')
const headNext = head.next
if (!headNext) throw new Error('headNext is null')
// 1. 断绝head和next的关系
delete headNext.prev
delete head.next
// 2. 重新赋值 listHead
this.listHead = headNext
// 3. 清理 data, 重新计数
delete this.data[head.key]
this.dataLength -= 1
}
}
get(key: string):any {
const data = this.data
const curNode = data[key]
if (!curNode) return null
if (this.listTail === curNode) {
return curNode.value
}
// curNode 移动到末尾
this.moveToTail(curNode)
return curNode.value
}
set(key: string, value: any) {
const data = this.data
const curNode = data[key]
if (!curNode) {
// 新增数据
const newNode: IListNode = {key, value}
// 移动到末尾
this.moveToTail(newNode)
data[key] = newNode
this.dataLength ++
if (this.dataLength === 1) this.listHead = newNode
} else {
// 修改现有数据
curNode.value = value
// 移动到末尾
this.moveToTail(curNode)
}
// 长度超过了,则需要清理
this.tryClean()
}
}
const lc = new LRUCache2(2)
lc.set('1', 1) // { 1=1 }
lc.set('2', 2) // { 1=1, 2=2 }
console.log(lc.get('1')) // 1 {2=2, 1=1} 真实结构比这个复杂,下同
lc.set('3',3) // {1=1, 3=3}
console.log(lc.get('2')) // null
lc.set('4',4) // {3=3, 4=4}
console.log(lc.get('1')) // null
console.log(lc.get('3')) // 3 {4=4, 3=3}
console.log(lc.get('4')) // 4 {3=3, 4=4}
- 数据结构设计:data 和 list 分别存储什么
- 双向链表的操作非常繁琐,代码很容易写错, 不易调试
- 链表 node 要存储 node.key, 否则需要遍历 data 删除
Python3 版算法实现
1 )方案1
class LRUCache:
def __init__(self, length):
if length < 1:
raise ValueError('Invalid length')
self.length = length
self.data = {}
self.order = []
def set(self, key, value):
if key in self.data:
self.order.remove(key)
elif len(self.data) >= self.length:
# 如果超出了容量,则删除最老的元素
oldest_key = self.order.pop(0)
del self.data[oldest_key]
self.data[key] = value
self.order.append(key)
def get(self, key):
if key not in self.data:
return None
# 保持最新的
self.order.remove(key)
self.order.append(key)
return self.data[key]
# 测试用例
lru_cache = LRUCache(2)
lru_cache.set(1, 1) # { 1=1 }
lru_cache.set(2, 2) # { 1=1, 2=2 }
print(lru_cache.get(1)) # 1 {2=2, 1=1}
lru_cache.set(3, 3) # {1=1, 3=3}
print(lru_cache.get(2)) # None
lru_cache.set(4, 4) # {3=3, 4=4}
print(lru_cache.get(1)) # None
print(lru_cache.get(3)) # 3 {4=4, 3=3}
print(lru_cache.get(4)) # 4 {3=3, 4=4}
2 )方案2
class ListNode:
def __init__(self, key, value):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache2:
def __init__(self, length):
if length < 1:
raise ValueError('Invalid length')
self.length = length
self.data = {}
self.data_length = 0
self.list_head = None
self.list_tail = None
def move_to_tail(self, cur_node):
tail = self.list_tail
if tail == cur_node:
return
# 1. 让 pre next 断绝与 curNode的关系
pre_node = cur_node.prev
next_node = cur_node.next
if pre_node:
if next_node:
pre_node.next = next_node
else:
pre_node.next = None
if next_node:
if pre_node:
next_node.prev = pre_node
else:
next_node.prev = None
if self.list_head == cur_node:
self.list_head = next_node
# 2. 让 curNode 断绝与 prev, next的关系
cur_node.prev = None
cur_node.next = None
# 3. 在list 末尾重新建立 curNode 的新关系
if tail:
tail.next = cur_node
cur_node.prev = tail
self.list_tail = cur_node
def try_clean(self):
while self.data_length > self.length:
head = self.list_head
if not head:
raise ValueError('Head is null')
head_next = head.next
if not head_next:
raise ValueError('HeadNext is null')
# 1. 断绝head和next的关系
head_next.prev = None
head.next = None
# 2. 重新赋值 listHead
self.list_head = head_next
# 3. 清理 data, 重新计数
del self.data[head.key]
self.data_length -= 1
def get(self, key):
cur_node = self.data.get(key)
if not cur_node:
return None
if self.list_tail == cur_node:
return cur_node.value
# curNode 移动到末尾
self.move_to_tail(cur_node)
return cur_node.value
def set(self, key, value):
cur_node = self.data.get(key)
if not cur_node:
# 新增数据
new_node = ListNode(key, value)
# 移动到末尾
self.move_to_tail(new_node)
self.data[key] = new_node
self.data_length += 1
if self.data_length == 1:
self.list_head = new_node
else:
# 修改现有数据
cur_node.value = value
# 移动到末尾
self.move_to_tail(cur_node)
# 长度超过了,则需要清理
self.try_clean()
# 测试用例
lc = LRUCache2(2)
lc.set('1', 1) # { 1=1 }
lc.set('2', 2) # { 1=1, 2=2 }
print(lc.get('1')) # 1 {2=2, 1=1}
lc.set('3', 3) # {1=1, 3=3}
print(lc.get('2')) # null
lc.set('4', 4) # {3=3, 4=4}
print(lc.get('1')) # null
print(lc.get('3')) # 3 {4=4, 3=3}
print(lc.get('4')) # 4 {3=3, 4=4}
Golang 版算法实现
1 )方案1
package main
import (
"errors"
"fmt"
)
type LRUCache struct {
length int
data map[interface{}]interface{}
order []interface{}
}
func NewLRUCache(length int) (*LRUCache, error) {
if length < 1 {
return nil, errors.New("invalid length")
}
return &LRUCache{
length: length,
data: make(map[interface{}]interface{}),
order: make([]interface{}, 0),
}, nil
}
func (cache *LRUCache) Set(key interface{}, value interface{}) {
if _, exists := cache.data[key]; exists {
// 如果键已存在,先从 order 中移除该键
for i, k := range cache.order {
if k == key {
cache.order = append(cache.order[:i], cache.order[i+1:]...)
break
}
}
} else if len(cache.data) >= cache.length {
// 如果超出了容量,则删除最老的元素
oldestKey := cache.order[0]
delete(cache.data, oldestKey)
cache.order = cache.order[1:]
}
cache.data[key] = value
cache.order = append(cache.order, key)
}
func (cache *LRUCache) Get(key interface{}) interface{} {
value, exists := cache.data[key]
if !exists {
return nil
}
// 保持最新的
for i, k := range cache.order {
if k == key {
cache.order = append(cache.order[:i], cache.order[i+1:]...)
break
}
}
cache.order = append(cache.order, key)
return value
}
func main() {
lruCache, _ := NewLRUCache(2)
lruCache.Set(1, 1) // { 1=1 }
lruCache.Set(2, 2) // { 1=1, 2=2 }
fmt.Println(lruCache.Get(1)) // 1 {2=2, 1=1}
lruCache.Set(3, 3) // {1=1, 3=3}
fmt.Println(lruCache.Get(2)) // null
lruCache.Set(4, 4) // {3=3, 4=4}
fmt.Println(lruCache.Get(1)) // null
fmt.Println(lruCache.Get(3)) // 3 {4=4, 3=3}
fmt.Println(lruCache.Get(4)) // 4 {3=3, 4=4}
}
2 )方案2
package main
import (
"errors"
"fmt"
)
type ListNode struct {
key, value interface{}
prev, next *ListNode
}
type LRUCache2 struct {
length int
data map[string]*ListNode
dataLength int
listHead, listTail *ListNode
}
func NewLRUCache2(length int) (*LRUCache2, error) {
if length < 1 {
return nil, errors.New("invalid length")
}
return &LRUCache2{
length: length,
data: make(map[string]*ListNode),
}, nil
}
func (cache *LRUCache2) moveToTail(curNode *ListNode) {
tail := cache.listTail
if tail == curNode {
return
}
// 1. 让 pre next 断绝与 curNode的关系
preNode := curNode.prev
nextNode := curNode.next
if preNode != nil {
if nextNode != nil {
preNode.next = nextNode
} else {
preNode.next = nil
}
}
if nextNode != nil {
if preNode != nil {
nextNode.prev = preNode
} else {
nextNode.prev = nil
}
if cache.listHead == curNode {
cache.listHead = nextNode
}
}
// 2. 让 curNode 断绝与 prev, next的关系
curNode.prev = nil
curNode.next = nil
// 3. 在list 末尾重新建立 curNode 的新关系
if tail != nil {
tail.next = curNode
curNode.prev = tail
}
cache.listTail = curNode
}
func (cache *LRUCache2) tryClean() {
for cache.dataLength > cache.length {
head := cache.listHead
if head == nil {
panic("head is null")
}
headNext := head.next
if headNext == nil {
panic("headNext is null")
}
// 1. 断绝head和next的关系
headNext.prev = nil
head.next = nil
// 2. 重新赋值 listHead
cache.listHead = headNext
// 3. 清理 data, 重新计数
delete(cache.data, head.key.(string))
cache.dataLength--
}
}
func (cache *LRUCache2) Get(key string) interface{} {
curNode, exists := cache.data[key]
if !exists {
return nil
}
if cache.listTail == curNode {
return curNode.value
}
// curNode 移动到末尾
cache.moveToTail(curNode)
return curNode.value
}
func (cache *LRUCache2) Set(key string, value interface{}) {
curNode, exists := cache.data[key]
if !exists {
// 新增数据
newNode := &ListNode{key: key, value: value}
// 移动到末尾
cache.moveToTail(newNode)
cache.data[key] = newNode
cache.dataLength++
if cache.dataLength == 1 {
cache.listHead = newNode
}
} else {
// 修改现有数据
curNode.value = value
// 移动到末尾
cache.moveToTail(curNode)
}
// 长度超过了,则需要清理
cache.tryClean()
}
func main() {
lc, _ := NewLRUCache2(2)
lc.Set("1", 1) // { 1=1 }
lc.Set("2", 2) // { 1=1, 2=2 }
fmt.Println(lc.Get("1")) // 1 {2=2, 1=1}
lc.Set("3", 3) // {1=1, 3=3}
fmt.Println(lc.Get("2")) // null
lc.Set("4", 4) // {3=3, 4=4}
fmt.Println(lc.Get("1")) // null
fmt.Println(lc.Get("3")) // 3 {4=4, 3=3}
fmt.Println(lc.Get("4")) // 4 {3=3, 4=4}
}
631



被折叠的 条评论
为什么被折叠?



