雙鏈表

來看雙向鏈表的實(shí)現(xiàn)
首先定義Node
/// 雙向列表的節(jié)點(diǎn)
class linkedNode<T> {
var value: T
var previous: linkedNode?
var next: linkedNode?
init(_ value: T) {
self.value = value
}
}
List
class linkedList<T> {
typealias Node = linkedNode<T>
private var head: Node?
private var tail: Node?
/// 判斷是否為空
var isEmpty: Bool {
return head == nil
}
/// 獲取頭節(jié)點(diǎn)
var first: Node?{
return head
}
///獲取尾節(jié)點(diǎn)
var last: Node? {
return tail
}
///獲取 鏈表數(shù)量
var count: Int = 0
// 下標(biāo)語法糖
subscript(index: Int) -> T? {
var i = index
var next = head
while i > 0 {
i -= 1
next = next?.next
}
return next?.value
}
}
尾插法
1 tail.next = new Node
2 new Node.previous = tail
這里主要注意 頭尾節(jié)點(diǎn)的問題
/// 尾插
func append(_ value: T) {
let newNode = Node(value)
if let lastNode = tail {
lastNode.next = newNode
newNode.previous = lastNode
tail = newNode
}else {
head = newNode
tail = newNode
}
//修改數(shù)量
count += 1
}
中插

///插入 node
func insert(value: T, atIndex index: Int) {
let (pre,next) = nodesBeforeAndAfter(index: index)
let newNode = Node(value)
pre?.next = newNode
next?.previous = newNode
newNode.previous = pre
newNode.next = next
if pre == nil {
head = newNode
}
if count == index - 1 {
tail = newNode
}
count += 1
}
///獲取某節(jié)點(diǎn)的頭結(jié)點(diǎn)和尾節(jié)點(diǎn)
private func nodesBeforeAndAfter(index: Int) -> (Node?,Node?) {
var next = head
var previous: Node?
var i = index
while i > 0 && next?.next != nil{
i -= 1
previous = next
next = next?.next
}
return (previous,next)
}
刪除節(jié)點(diǎn)
///刪除最后一個(gè)
func removeLast() {
removeNode(node: last!)
}
/// 移除某一個(gè)node
func removeNode(node: Node) {
let pre = node.previous
let next = node.next
pre?.next = next
next?.previous = pre
if pre == nil {
head = node.next
}
if next == nil {
tail = node.previous
}
count -= 1
}
移動(dòng)到頭結(jié)點(diǎn)
/// node 移動(dòng)到頭節(jié)點(diǎn)
func moveToHead(node: Node) {
let pre = node.previous
let next = node.next
pre?.next = next
next?.previous = pre
node.next = head
head?.previous = node
head = node
if pre == nil {
return
}
if next == nil {
tail = pre
}
}
LRU
準(zhǔn)備工作差不多了
我來看下LRU的定義

新數(shù)據(jù)插入到鏈表頭部;
每當(dāng)緩存命中(即緩存數(shù)據(jù)被訪問),則將數(shù)據(jù)移到鏈表頭部;
當(dāng)鏈表滿的時(shí)候,將鏈表尾部的數(shù)據(jù)丟棄。
【命中率】
當(dāng)存在熱點(diǎn)數(shù)據(jù)時(shí),LRU的效率很好,但偶發(fā)性的、周期性的批量操作會(huì)導(dǎo)致LRU命中率急劇下降,緩存污染情況比較嚴(yán)重。
【復(fù)雜度】
實(shí)現(xiàn)簡(jiǎn)單。
【代價(jià)】
命中時(shí)需要遍歷鏈表,找到命中的數(shù)據(jù)塊索引,然后需要將數(shù)據(jù)移到頭部。
LRU 實(shí)踐
我們客戶端關(guān)于LRU算法 最先想到,肯定是圖片緩存系統(tǒng)
為了方便
我們存的數(shù)據(jù) 為["1": "我是一張圖片"] 這種【String: Stirng】
首先我們定義一個(gè)簡(jiǎn)單的緩存類
/// LRU 實(shí)現(xiàn)
class CacheLRU<T> {
var limit: Int
var cache: linkedList<T>
init(cacheNumber: Int) {
self.limit = cacheNumber
self.cache = linkedList<T>()
}
//增
func add(some: T) {
if cache.count == limit {
cache.removeLast()
}
cache.append(some)
}
//刪
func clearCache() {
cache.removeAll()
}
//移
func move(value: linkedNode<T>) {
cache.moveToHead(node: value)
}
}
// 1 先從緩存系統(tǒng)查看是否有緩存
extension linkedList where T == Dictionary<String, String> {
func contains(name: String) -> Node? {
var next = head
var index = 0
while index < count {
if next?.value[name] != nil {
return next
}
index += 1
next = next?.next
}
return nil
}
}
extension CacheLRU where T == Dictionary<String, String> {
func fetchImage(name: String) {
let node = cache.contains(name: name)
if let dic = node?.value {
// 1.內(nèi)存存在 直接取內(nèi)存中的數(shù)據(jù)
print(dic[name] ?? "??")
// 2.lru 訪問的數(shù)據(jù)成為頭結(jié)點(diǎn)
move(value: node!)
}else {
//網(wǎng)絡(luò)獲取 并且 add
}
}
}
我們來看結(jié)果
1 創(chuàng)建緩存類 存貯圖片
這里設(shè)置存貯上線為2張圖片(實(shí)際肯定是內(nèi)存為標(biāo)準(zhǔn))
let cacheMnager = CacheLRU<Dictionary<String,String>>.init(cacheNumber: 2)
let image1 = ["1": "我是一張圖片"]
let image2 = ["2": "我是一張圖片"]
let image3 = ["3": "我是一張圖片"]
cacheMnager.add(some: image1)
cacheMnager.add(some: image2)
cacheMnager.add(some: image3)
因?yàn)樯暇€是2 導(dǎo)致 第二張圖片被清除

2 訪問已有圖片
cacheMnager.fetchImage(name: "3")
訪問已有圖片的 已有圖片的node 會(huì)移到頭部

LRU-K

LRU-K中的K代表最近使用的次數(shù),因此LRU可以認(rèn)為是LRU-1。LRU-K的主要目的是為了解決LRU算法“緩存污染”的問題,其核心思想是將“最近使用過1次”的判斷標(biāo)準(zhǔn)擴(kuò)展為“最近使用過K次”。
實(shí)現(xiàn)上: LRU-K 的其實(shí)就是 在LRU的基礎(chǔ)上多維護(hù)一條歷史鏈表。鏈表里面的數(shù)據(jù)多一個(gè)訪問次數(shù)屬性。當(dāng)訪問次數(shù)打到K次。就存到緩存隊(duì)列里 這里和LRU一樣
【命中率】
LRU-K降低了“緩存污染”帶來的問題,命中率比LRU要高。
【復(fù)雜度】
LRU-K隊(duì)列是一個(gè)優(yōu)先級(jí)隊(duì)列,算法復(fù)雜度和代價(jià)比較高。
【代價(jià)】
由于LRU-K還需要記錄那些被訪問過、但還沒有放入緩存的對(duì)象,因此內(nèi)存消耗會(huì)比LRU要多;當(dāng)數(shù)據(jù)量很大的時(shí)候,內(nèi)存消耗會(huì)比較可觀。
LRU-K需要基于時(shí)間進(jìn)行排序(可以需要淘汰時(shí)再排序,也可以即時(shí)排序),CPU消耗比LRU要高。
我們看代碼實(shí)現(xiàn)
class CacheLRU_K<T,E> {
let cache: CacheLRU<E>
let cacheHistory: CacheLRU<T>
let limit: Int
let K: Int
init(limit: Int,K: Int) {
self.K = K
self.limit = limit
self.cache = CacheLRU.init(cacheNumber: limit)
self.cacheHistory = CacheLRU.init(cacheNumber: limit)
}
}
extension linkedList where T == Dictionary<String,Any> {
func contains(name: String) -> T? {
var next = head
var index = 0
while index < count {
if next?.value[name] != nil {
return next?.value
}
index += 1
next = next?.next
}
return nil
}
}
extension CacheLRU_K where T == Dictionary<String,Any>, E == [String: String] {
func add(some: T) {
var data: [String: Any] = some
let name = some.first!.key
let temp = cacheHistory.cache.contains(name: name)
if let temp = temp {
data = temp
}
if data["number"] == nil {
data["number"] = 0
}
var number = data["number"] as! Int
data["number"] = number + 1
number += 1
if number == K {
data["number"] = nil
let temp = data as! [String: String]
cache.add(some: temp)
//cacheHistory remove
//下班了 以后補(bǔ)上0.0
}else {
if cacheHistory.cache.count == limit {
cacheHistory.cache.removeLast()
}
cacheHistory.add(some: data)
}
}
func fetchImage(name: String) {
let node = cache.cache.contains(name: name)
if let dic = node?.value {
// 1.內(nèi)存存在 直接取內(nèi)存中的數(shù)據(jù)
print(dic[name] ?? "??")
// 2.lru 訪問的數(shù)據(jù)成為頭結(jié)點(diǎn)
cache.move(value: node!)
}else {
//這里和lru 不同
//網(wǎng)絡(luò)請(qǐng)求
// lru_k add
}
}
}
這里 基本和LRU 一樣就是維護(hù)一條歷史隊(duì)列
這里初始化 K 為2 意思
只有連續(xù)add 2次才能真正的假如到緩存鏈表中
let manager = CacheLRU_K<[String:Any],[String:String]>.init(limit: 2, K: 2)
let image11 = ["1": "我是一張圖片"]
manager.add(some: image11)
緩存和 歷史緩存的結(jié)果

繼續(xù)add
manager.add(some: image11)
這時(shí)候內(nèi)存中就存在了我要要緩存的數(shù)據(jù)

歷史緩存中就應(yīng)該刪除這條記錄
下班了 溜了。。實(shí)現(xiàn)過程可能沒講清楚。但是代碼都注釋的聽明白的。有時(shí)間補(bǔ)上