概述
我们的目标是实现一个缓存,该缓存将
-
它应该支持Set和Get操作
-
设置和获取的时间复杂度为O(1)
-
假设缓存的最大容量是3,一旦缓存满了,还有一个钥匙要插入,那么需要从缓存中删除一个现有的条目。
-
删除应该基于驱逐算法 -LRU
实现细节
-
我们将使用Map和Doubly-linked List来存储一切。地图和双链表的使用是为了使获取和设置操作达到O(1),即使有驱逐。
-
Map的键是字符串,值是指向双链表的节点的指针。
-
Doubly Linked List的每个节点都包含键和值。每个节点还将有一个指向双链表中前一个节点的指针以及指向双链表中下一个节点的指针
让我们看看Get和Set是如何在O(1)时间内工作的
Set(key int, value int)
对于任何设置操作,它将首先创建一个双链表节点,并提供key和value。然后将在地图中建立一个条目,将key作为输入键,value作为节点的地址。一旦节点被创建,就会出现两种情况
-
缓存未满 - 在这种情况下,它将把控制权交给当前的驱逐算法,即LRU。LRU算法要做的是将该节点插入到一个双链接列表的最后。我们的想法是,双链表中的前面节点是最近使用最少的节点。这里的每个操作都是O(1)。
-
缓存已满--在这种情况下,它将把控制权交给当前的驱逐算法,即LRU。它将驱逐最近使用最少的节点,也就是前面的节点。一旦该节点被驱逐,它将在最后插入新的节点。这里的每个操作都是O(1)。
Get(key int)
对于任何Get操作,它将首先检查地图中是否存在给定的key。如果它存在,那么它将获取地图中key所指向的节点的地址。然后,它将从该节点中获取值。然后,它将把控制权交给当前的驱逐算法,即LRU。LRU算法将把当前节点移到双链接列表的最后。同样,我们的想法是,双链表的前面的节点是最近使用最少的节点,为了保持当前节点被移动到最后。这里的每个操作都是O(1)。
程序
如果有人感兴趣,这里是go编程语言的完整工作代码。
doublelylinklist.go
package main
import "fmt"
type node struct {
key string
value string
prev *node
next *node
}
type doublyLinkedList struct {
len int
tail *node
head *node
}
func initDoublyList() *doublyLinkedList {
return &doublyLinkedList{}
}
func (d *doublyLinkedList) AddToFront(key, value string) {
newNode := &node{
key: key,
value: value,
}
if d.head == nil {
d.head = newNode
d.tail = newNode
} else {
newNode.next = d.head
d.head.prev = newNode
d.head = newNode
}
d.len++
return
}
func (d *doublyLinkedList) RemoveFromFront() {
if d.head == nil {
return
} else if d.head == d.tail {
d.head = nil
d.tail = nil
} else {
d.head = d.head.next
}
d.len--
}
func (d *doublyLinkedList) AddToEnd(node *node) {
newNode := node
if d.head == nil {
d.head = newNode
d.tail = newNode
} else {
currentNode := d.head
for currentNode.next != nil {
currentNode = currentNode.next
}
newNode.prev = currentNode
currentNode.next = newNode
d.tail = newNode
}
d.len++
}
func (d *doublyLinkedList) Front() *node {
return d.head
}
func (d *doublyLinkedList) MoveNodeToEnd(node *node) {
prev := node.prev
next := node.next
if prev != nil {
prev.next = next
}
if next != nil {
next.prev = prev
}
if d.tail == node {
d.tail = prev
}
if d.head == node {
d.head = next
}
node.next = nil
node.prev = nil
d.len--
d.AddToEnd(node)
}
func (d *doublyLinkedList) TraverseForward() error {
if d.head == nil {
return fmt.Errorf("TraverseError: List is empty")
}
temp := d.head
for temp != nil {
fmt.Printf("key = %v, value = %v, prev = %v, next = %v\n", temp.key, temp.value, temp.prev, temp.next)
temp = temp.next
}
fmt.Println()
return nil
}
func (d *doublyLinkedList) Size() int {
return d.len
}
evictionAlgorithm.go
package main
type evictionAlgo interface {
evict(c *Cache) string
get(node *node, c *Cache)
set(node *node, c *Cache)
set_overwrite(node *node, value string, c *Cache)
}
func createEvictioAlgo(algoType string) evictionAlgo {
if algoType == "fifo" {
return &fifo{}
} else if algoType == "lru" {
return &lru{}
}
return nil
}
lru.go
package main
import "fmt"
type lru struct {
}
func (l *lru) evict(c *Cache) string {
key := c.doublyLinkedList.Front().key
fmt.Printf("Evicting by lru strtegy. Evicted Node Key: %s: ", key)
c.doublyLinkedList.RemoveFromFront()
return key
}
func (l *lru) get(node *node, c *Cache) {
fmt.Println("Shuffling doubly linked list due to get operation")
c.doublyLinkedList.MoveNodeToEnd(node)
}
func (l *lru) set(node *node, c *Cache) {
fmt.Println("Shuffling doubly linked list due to set operation")
c.doublyLinkedList.AddToEnd(node)
}
func (l *lru) set_overwrite(node *node, value string, c *Cache) {
fmt.Println("Shuffling doubly linked list due to set_overwrite operation")
node.value = value
c.doublyLinkedList.MoveNodeToEnd(node)
}
fifo.go
package main
import "fmt"
type fifo struct {
}
func (l *fifo) evict(c *Cache) string {
fmt.Println("Evicting by fifo strtegy")
key := c.doublyLinkedList.Front().key
c.doublyLinkedList.RemoveFromFront()
return key
}
func (l *fifo) get(node *node, c *Cache) {
fmt.Println("Shuffling doubly linked list due to get operation")
}
func (l *fifo) set(node *node, c *Cache) {
fmt.Println("Shuffling doubly linked list due to set operation")
c.doublyLinkedList.AddToEnd(node)
}
func (l *fifo) set_overwrite(node *node, value string, c *Cache) {
fmt.Println("Shuffling doubly linked list due to set_overwrite operation")
}
cache.go
package main
import "fmt"
type Cache struct {
doublyLinkedList *doublyLinkedList
storage map[string]*node
evictionAlgo evictionAlgo
capacity int
maxCapacity int
}
func initCache(evictionAlgo evictionAlgo, maxCapacity int) Cache {
storage := make(map[string]*node)
return Cache{
doublyLinkedList: &doublyLinkedList{},
storage: storage,
evictionAlgo: evictionAlgo,
capacity: 0,
maxCapacity: maxCapacity,
}
}
func (this *Cache) setEvictionAlgo(e evictionAlgo) {
this.evictionAlgo = e
}
func (this *Cache) set(key, value string) {
node_ptr, ok := this.storage[key]
if ok {
this.evictionAlgo.set_overwrite(node_ptr, value, this)
return
}
if this.capacity == this.maxCapacity {
evictedKey := this.evict()
delete(this.storage, evictedKey)
}
node := &node{key: key, value: value}
this.storage[key] = node
this.evictionAlgo.set(node, this)
this.capacity++
}
func (this *Cache) get(key string) string {
node_ptr, ok := this.storage[key]
if ok {
this.evictionAlgo.get(node_ptr, this)
return (*node_ptr).value
}
return ""
}
func (this *Cache) evict() string {
key := this.evictionAlgo.evict(this)
this.capacity--
return key
}
func (this *Cache) print() {
for k, v := range this.storage {
fmt.Printf("key :%s value: %s\n", k, (*v).value)
}
this.doublyLinkedList.TraverseForward()
}
main.go
package main
import "fmt"
func main() {
lru := createEvictioAlgo("lru")
cache := initCache(lru, 3)
cache.set("a", "1")
cache.print()
cache.set("b", "2")
cache.print()
cache.set("c", "3")
cache.print()
value := cache.get("a")
fmt.Printf("key: a, value: %s\n", value)
cache.print()
cache.set("d", "4")
cache.print()
cache.set("e", "5")
cache.print()
}
输出
Shuffling doubly linked list due to set operation
key :a value: 1
key = a, value = 1, prev = , next =
Shuffling doubly linked list due to set operation
key :a value: 1
key :b value: 2
key = a, value = 1, prev = , next = &{b 2 0xc00007e1e0 }
key = b, value = 2, prev = &{a 1 0xc00007e210}, next =
Shuffling doubly linked list due to set operation
key :a value: 1
key :b value: 2
key :c value: 3
key = a, value = 1, prev = , next = &{b 2 0xc00007e1e0 0xc00007e2a0}
key = b, value = 2, prev = &{a 1 0xc00007e210}, next = &{c 3 0xc00007e210 }
key = c, value = 3, prev = &{b 2 0xc00007e1e0 0xc00007e2a0}, next =
Shuffling doubly linked list due to get operation
key: a, value: 1
key :a value: 1
key :b value: 2
key :c value: 3
key = b, value = 2, prev = , next = &{c 3 0xc00007e210 0xc00007e1e0}
key = c, value = 3, prev = &{b 2 0xc00007e2a0}, next = &{a 1 0xc00007e2a0 }
key = a, value = 1, prev = &{c 3 0xc00007e210 0xc00007e1e0}, next =
Evicting by lru strtegy. Evicted Node Key: %s: b
Shuffling doubly linked list due to set operation
key :d value: 4
key :c value: 3
key :a value: 1
key = c, value = 3, prev = &{b 2 0xc00007e2a0}, next = &{a 1 0xc00007e2a0 0xc00007e450}
key = a, value = 1, prev = &{c 3 0xc00007e210 0xc00007e1e0}, next = &{d 4 0xc00007e1e0 }
key = d, value = 4, prev = &{a 1 0xc00007e2a0 0xc00007e450}, next =
Evicting by lru strtegy. Evicted Node Key: %s: c
Shuffling doubly linked list due to set operation
key :a value: 1
key :d value: 4
key :e value: 5
key = a, value = 1, prev = &{c 3 0xc00007e210 0xc00007e1e0}, next = &{d 4 0xc00007e1e0 0xc00007e570}
key = d, value = 4, prev = &{a 1 0xc00007e2a0 0xc00007e450}, next = &{e 5 0xc00007e450 }
key = e, value = 5, prev = &{d 4 0xc00007e1e0 0xc00007e570}, next =
总结
这就是关于在Golang中实现LRU缓存的全部内容。希望你喜欢这篇文章。请在评论中分享反馈