LRU算法 :學習筆記
LRU是什麼
LRU(Least Recently Used)即最近最少使用,是一種緩存算法(頁面置換算法)。我們知道,緩存通常是具有固定大小的,他應該只保存那些常常被用到的數據,而數據如何更新則是通過緩存算法實現,LRU算法就是一種簡單,常用的緩存算法。
原理
LRU算法是核心思想是:如果一個數據在最近一段時間都沒有被用到,那麼它在將來被使用到的可能性也很小。故當緩存空間已滿的情況下,我們需要淘汰掉最久沒有被訪問到的數據。理想的LRU算法讀寫是時間複雜度應該都爲O(1)。
實現
爲了達到理想的性能,我們需要一種既可以按訪問順序排序,又可以在常數時間隨機訪問的數據結構。這裏可以採用HashMap和雙向鏈表實現。HashMap可以存儲Key,可以在常數時間裏讀寫Key,而Value用來指向雙向鏈表的節點,爲了在常數時間裏移除一個節點我們還需要Head節點和Tril節點。
- put(key,value)
首先在HashMap中查找Key如果存在,說明數據已在緩存中,我們只需要更新節點的值,並將節點放到鏈表頭部即可。如果不存在說明數據不在緩存中,則需要構造節點,並將其放置在頭部。在這個過程中,如果發現緩存已滿,則需要淘汰掉鏈表尾部的數據並在HashMap中移除相應的Key。 - get(key)
通過HashMap查找對應的節點,將其移動至頭部並返回。
代碼實現如下:
class LruCache<K, V>() {
private data class Node<K, V>(
var key: K? = null,
var value: V? = null,
var prev: Node<K, V>? = null,
var next: Node<K, V>? = null
)
private val hashMap: HashMap<K, Node<K, V>> = hashMapOf()
private var count = 0
private var capacity = 8
private val head: Node<K, V> = Node()
private val tail: Node<K, V> = Node()
init {
head.next = tail
tail.prev = head
}
constructor(capacity: Int) : this() {
this.capacity = capacity
}
fun get(key: K): V? {
val node = hashMap[key] ?: return null
move(node)
return node.value
}
fun put(key: K, value: V) {
val node = hashMap[key]
if (node == null) {
val newNode = Node(key, value)
add(newNode)
hashMap[key] = newNode
++count
if (count > capacity) {
val deleteNode = delete()
hashMap.remove(deleteNode.key)
--count
}
} else {
node.value = value
move(node)
}
}
private fun add(node: Node<K, V>) {
node.prev = head
node.next = head.next
head.next!!.prev = node
head.next = node
}
private fun remove(node: Node<K, V>) {
val prev = node.prev!!
val next = node.next!!
prev.next = next
next.prev = prev
}
private fun move(node: Node<K, V>) {
remove(node)
add(node)
}
private fun delete(): Node<K, V> {
val node = tail.prev!!
remove(node)
return node
}
}
而在實際使用中,我們可以使用LinkedHashMap實現,其內部就是使用雙向鏈表,我們只需稍作修改便能使用。
在LinkedHashMap的構造參數(initialCapacity:Int, loadFactor:Float,accessOrder:Boolean)
中,initialCapacity
是HashMap的初始大小,loadFactor
則是裝載因子,accessOrder=false
表示基於插入順序,accessOrder=true
表示基於訪問順序。
實現LRU的關鍵方法:
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>?): Boolean {
return size > capacity
}
以上表示當LinkedHashMap大小超過我們設定的大小時,移除鏈表首部的節點
class LruChche<K, V>(private val capacity: Int = 8) {
private var hashMap: LinkedHashMap<K, V> = object : LinkedHashMap<K, V>
(capacity / 0.75.toInt() + 1, 0.75f, true) {
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>?): Boolean {
return size > capacity
}
}
fun get(key: K): V? = hashMap[key]
fun put(key: K, value: V) {
hashMap[key] = value
}
}
結語
第一次聽說LRU算法是在現代操作系統這本書中,但引起我深究的是Glide這個庫在自定義Model的時候,便有了一探究竟的想法,故整理資料寫下這些文字,一面是爲了加深自己的影響,另一面也希望我所說的能讓大家更簡單的去理解LRU,一起學習。