篳路藍縷,以啓山林。撫有蠻夷,以屬華夏。不鳴則已,一鳴驚人。
——《左傳`宣公十二年》
目錄
雙向鏈表實現可移步:雙向鏈表
LRU
LRU(Least Recently Used):是一種按訪問時序的緩存淘汰策略:最近使用過的數據是有用的,很久沒用的數據是無用的,內存滿了時優先刪除最久沒用的那個數據。
情景再現:
舉例說明下。假如你的手機運存所支持的當前緩存的最大後臺程序數爲4,你由久及近依次打開了計算器、微信、手電筒、日曆四個程序,即最新打開的是日曆,最久打開的是計算器。
操作1:你現在重新點開了“微信”,這時候你再查看後臺當前運行的程序列表,由久及近應該會變爲計算器、手電筒、日曆、微信,即最新打開的“微信”程序被排在了開頭, 其他的依次後退;
操作2:你現在打開了一個新程序“時鐘”,那麼後臺程序列表將變成:手電筒、日曆、微信、時鐘 ,按照LRU策略,因爲緩存滿了,最久沒有使用的是計算器,系統會自動把計算器刪除出去,時鐘是新打開的所以排在了最開頭,剛剛排在開頭的日曆排到了第二位。
操作3:我主動划走了(刪除了)後臺的日曆程序,那麼由久及近將變成:手電筒、微信、時鐘
總結與設計
特點如下:
1,獲取其中某個數據,該數據被提到開頭,其他的繼續按原順序接上
2,這組數據有順序,刪除其中一個其他的繼續按原順序接上
3,這組數據如果滿了(達到了存儲它的最大長度),此時再往裏添加一個數據,那麼尾部最後一個數據(最久未使用的)將被刪除,新添加的將放在首部
刪除元素節點操作可以用鏈表,鏈表的刪除和插入操作快並且是有順序的;哈希表查找(訪問)某個元素快,因此可將二者結合取長補短,形成哈希鏈表。 因此設計如下:
鏈表表首<-->[k1:v1]<-->[k2:v2]<-->鏈表表尾
哈希表存儲結構:基於map,與鏈表節點映射。
操作:如刪除k2,鏈表將變爲鏈表表首<-->[k1:v1]<-->鏈表表尾,同時哈希表中也將只剩k1一個鍵。
實現LRU
// 鏈表操作接口
type ListInterface interface {
Add2Front(string) // 鏈表表首添加新元素
Remove(string) // 刪除指定元素
RemoveLast(string) *Node // 刪除最後一個元素
Len(List) int // 鏈表長度
}
const Max = 5 //定義緩存的最大容量
var hashMap map[string]*Node // 定義字典
var cache *List // 定義緩存
type Node struct {
Pre *Node
Next *Node
Data DataStruct
}
type DataStruct struct {
Key string
Value string
}
type List struct {
First *Node
Last *Node
Size int
}
// 刪除鏈表尾節點
func (list *List) RemoveLast() *Node {
node := list.Last.Pre
lastNode := list.Last
node.Next = nil
list.Last = node
list.Size -= 1
return lastNode
}
// 刪除指定節點
func (list *List) Remove(value string) {
current := list.First
for current != nil {
if current.Data.Key == value {
pre := current.Pre
next := current.Next
pre.Next = next
next.Pre = pre
}
current = current.Next
}
list.Size -= 1
}
// 添加新的首節點
func (list *List) Add2Front(key, value string) {
newNode := new(Node)
newNode.Data.Key = key
newNode.Data.Value = value
// 鏈表爲空時
if list.Size < 1 {
list.First = newNode
list.Last = newNode
} else {
// 鏈表的長度>=1時
firstNode := list.First
firstNode.Pre = newNode
newNode.Next = firstNode
list.First = newNode
}
list.Size += 1
}
func (list *List) Len() int {
return list.Size
}
// 創建一個空的雙鏈表
func CreateNewAirList() (list *List) {
return &List{}
}
// 訪問/查找某個數據
func (cache *List) Get(key string) *Node {
if value, ok := hashMap[key]; ok { // 如果緩存中有
cache.Put(key, value.Data.Value) // 添加到表首
return value // 返回數據
}
return nil
}
// 添加數據/將數據提到開頭
func (cache *List) Put(key, value string) {
node := new(Node)
node.Data.Key = key
node.Data.Value = value
if _, ok := hashMap[key]; ok { // 先檢查,如果緩存中有
cache.Remove(key) // 刪除舊數據
cache.Add2Front(key, value) // 添加到表首
hashMap[key] = node // 更新map中的值
} else {
if cache.Size == Max { // 添加時容量已滿
cache.RemoveLast()
delete(hashMap, key)
}
cache.Add2Front(key, value) // 添加到表首
hashMap[key] = node //更新map中的值
}
}
// 刪除某元素。要刪除的後臺程序必然是存在的
func (cache *List) RemoveApp(key string) {
cache.Remove(key)
delete(hashMap, key)
}
// 查看當前運行的後臺程序列表
func (list *List) printNode() {
head := list.First
fmt.Print("\t\t")
for head != nil {
fmt.Print(head.Data, "\t")
head = head.Next
}
}
驗證下我的騷操作
func main() {
cache = CreateNewAirList()
hashMap = make(map[string]*Node, 5)
/* 測試新增 */
cache.Put("日曆", "日曆value")
cache.Put("計算器", "計算器value")
cache.Put("微信", "微信value")
cache.Put("時鐘", "時鐘value") // 最新打開的程序在表頭
fmt.Println("新開了四個程序,由近及久依次是:")
cache.printNode()
/* 測試訪問 */
fmt.Println("\n微信已在緩存中,再次訪問微信,後臺程序由近及久依次是:")
cache.Get("微信") // 訪問微信
cache.printNode() // 檢查微信是否成了表頭第一個程序,其他的順序是否正確
fmt.Println("\n新開了個手電筒程序:")
cache.Put("手電筒", "手電筒value")
cache.printNode()
/* 測試新增讓緩存佔滿 */
fmt.Println("當前緩存容量已滿:", cache.Len(), " \n新開了個程序陌陌,後臺程序由近及久依次是:")
cache.Put("陌陌", "陌陌value")
cache.printNode() //檢查陌陌是不是在開頭,表尾原來的日曆是否沒有了,其他幾個的順序是否正常的
/* 測試刪除緩存中某程序:划走(刪除)手電筒 */
fmt.Println("\n划走(刪除)手電筒程序後:")
cache.RemoveApp("手電筒")
cache.printNode()
fmt.Println("\n划走(刪除)時鐘程序後:")
cache.RemoveApp("時鐘")
cache.printNode()
}
控制檯
新開了四個程序,由近及久依次是:
{時鐘 時鐘value} {微信 微信value} {計算器 計算器value} {日曆 日曆value}
微信已在緩存中,再次訪問微信,後臺程序由近及久依次是:
{微信 微信value} {時鐘 時鐘value} {計算器 計算器value} {日曆 日曆value}
新開了個手電筒程序:
{手電筒 手電筒value} {微信 微信value} {時鐘 時鐘value} {計算器 計算器value} {日曆 日曆value} 當前緩存容量已滿: 5
新開了個程序陌陌,後臺程序由近及久依次是:
{陌陌 陌陌value} {手電筒 手電筒value} {微信 微信value} {時鐘 時鐘value} {計算器 計算器value}
划走(刪除)手電筒程序後:
{陌陌 陌陌value} {微信 微信value} {時鐘 時鐘value} {計算器 計算器value}
划走(刪除)時鐘程序後:
{陌陌 陌陌value} {微信 微信value} {計算器 計算器value}
Process finished with exit code 0