YYCache源碼閱讀以內存緩存設計

YYCache的總體結構是分爲兩部分:內存緩存和磁盤緩存。node

內存緩存提供容量小但高速的存取功能,磁盤緩存提供大容量但低速的持久化存儲。
算法

爲何須要緩存?

內存的讀寫速度遠大於磁盤的讀寫速度,將頻繁使用的數據存在內存中,再次用到的時候直接從內存中讀取,從而提升性能。數組

設計內存緩存的幾個要點

1. 緩存算法:根據場景選擇合適的緩存算法,才能使緩存有較高的命中率。常見的緩存算法有:LRU(最近最少使用)、LRU-2(相似LRU,准入門檻變成2次)、LFU(最不常用)、FIFO(先進先出)等等。緩存

2. 讀寫性能:相同狀況下,存儲數據和讀取數據的速度。安全

3. 線程安全:考慮到可能會在多個線程中讀寫緩存。bash

LRU算法


LRU算法(least recently used)顧名思義,淘汰掉最近最少使用的數據。異步

1. 訪問的數據未命中緩存,若是此時緩存未滿的時候。直接將新數據插到最前面(這也決定了基礎結構不能用數組,只能用鏈表,數組插入頭部時間複雜度爲O(n),鏈表插入頭部時間複雜度爲O(1))。async

2. 訪問的數據命中緩存,將訪問的數據插到鏈表的最前面。性能

3. 訪問的數據未命中數據,可是此時緩存已經滿了,此時須要先淘汰掉鏈表的尾部,而後再見新數據插到最前面。ui

YYModel內存緩存的設計

YYModel的內存緩存部分是YYMemoryCache這個類實現的。YYMemoryCache使用了LRU緩存算法,由hashMap+雙鏈表實現。由於hashMap的讀操做是O(1),用於保證讀取速度。雙鏈表的刪除和插入操做都是O(1),很好的保證了寫操做的速度。

爲何不用單鏈表?

1. 單鏈表的刪除操做須要讀到前一個節點,而讀取前一個節點須要從頭遍歷。

2. 有人會說,也不是吧,能夠將下一個節點的數據賦給當前節點,而後刪除下一個節點,這樣就至關於刪除了當前節點(刪除了當前節點的數據)。首先這樣有一遍拷貝數據的操做,若是刪除的是最後一個,這種作法就沒有了。而緩存算法的淘汰機制恰好刪除的都是最後一個。

YYModel結構


hashmap做用在於快速讀取。雙鏈表用於維護順序,實現淘汰機制。

LRU機制對應的僞代碼

1. 訪問的數據未命中緩存,若是此時緩存未滿的時候。直接將新數據插到最前面

if (!hashmap[@"key"]) {
    Node *node = [[Node alloc] init];
    node.data = data;
    hashmap[@"key"] = node;
    if (self.linkedList.head) {
        node.next = self.linkedList.head;
        node.prev = nil;
        self.linkedList.head.prev = node;
        self.linkedList.head = node;
    } else {
        node.prev = nil;
        node.next = nil;
        self.linkedList.head = node;
        self.linkedList.tail = node;
    }
}複製代碼

2. 訪問的數據命中緩存,將訪問的數據插到鏈表的最前面。

if (hashmap[@"key"]) {
    Node *node = hashmap[@"key"];

    if (node == self.linkedList.head) {
    	return;
    } else if (node == self.linkedList.tail) {
    	node.prev.next = nil;
    	self.linkedList.tail = node.prev;
    	node.next = self.linkedList.head;
    	self.linkedList.head.prev = node;
    	self.linkedList.head = node;
    } else {
    	node.prev.next = node.next;
    	node.next.prev = node.prev;
    	node.next = self.linkedList.head;
    	self.linkedList.head.prev = node;
    	self.linkedList.head = node;
    }
}複製代碼

3. 訪問的數據未命中數據,可是此時緩存已經滿了,此時須要先淘汰掉鏈表的尾部,而後再見新數據插到最前面。

Node *node = self.linkedList.tail;
node.prev.next = nil;
self.linkedList.tail = node.prev;
[hashmap removeObjectForKey:@"key"];
free(node);複製代碼

線程安全

1. 讀寫的時候加鎖保證線程安全

2. 刪除釋放節點在異步線程,提升性能。

if (_lru->_totalCost > _costLimit) {
    dispatch_async(_queue, ^{
    [self trimToCost:_costLimit];
    });
}複製代碼
相關文章
相關標籤/搜索