YYMemeryCache之LRU算法實(shí)現(xiàn)

1. LRU是什么台汇?

LRU 是Least Recently Used的縮寫,即最近最少使用篱瞎。其核心思想是最近最多使用的數(shù)據(jù)再將來更可能再次被使用苟呐,而最近最少使用的數(shù)據(jù)將來只有極小的概率再次被使用,所以當(dāng)緩存滿時(shí)將其移出緩存淘汰以達(dá)到緩存最多使用數(shù)據(jù)的目的俐筋,所以也叫內(nèi)存淘汰算法牵素。

2. LRU運(yùn)行原理圖示

LRU運(yùn)行原理.png

3. LRU數(shù)據(jù)結(jié)構(gòu)選定

再了解到LRU的運(yùn)行原理之后我們就可以開始用代碼實(shí)現(xiàn)這個(gè)算法,那我們用什么數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)呢澄者,我們從下面的兩方面來分析
這個(gè)算法需要涉及到兩個(gè)需求

  • 數(shù)據(jù)的查找
  • 數(shù)據(jù)的移動(dòng)
    1)數(shù)組實(shí)現(xiàn)
    如果我們用數(shù)組來實(shí)現(xiàn)的話笆呆,數(shù)據(jù)查找需要用for循環(huán)请琳,數(shù)據(jù)移動(dòng)也需要用for循環(huán)
    數(shù)據(jù)查找的時(shí)間復(fù)雜度: O(n),
    數(shù)據(jù)移動(dòng)的時(shí)間復(fù)雜度: O(n)赠幕,
    空間復(fù)雜度: O(n)
    那能優(yōu)化嘛俄精,當(dāng)然是可以的
    2)數(shù)組+哈希表實(shí)現(xiàn)
    我們在緩存數(shù)據(jù)的時(shí)候,將數(shù)據(jù)插入到數(shù)組同時(shí)榕堰,用keyValue的形式再保存到哈希表中竖慧,這樣在我們想查找某個(gè)數(shù)據(jù)的時(shí)候可以直接用keyValue的形式去哈希表中查找,從而將數(shù)據(jù)查找的時(shí)間復(fù)雜度降低
    數(shù)據(jù)查找的時(shí)間復(fù)雜度: O(1)
    數(shù)據(jù)移動(dòng)的時(shí)間復(fù)雜度: O(n)
    空間復(fù)雜度: O(n)
    那數(shù)據(jù)移動(dòng)能優(yōu)化嘛逆屡,也是可以的
    3)雙向鏈表+哈希表
    在我們哈希表命中數(shù)據(jù)的時(shí)候圾旨,要?jiǎng)h除移動(dòng)數(shù)據(jù)需要修改命中數(shù)據(jù)前驅(qū)(pre)的后繼(next),單鏈表在不能循環(huán)的條件下是不能拿到命中數(shù)據(jù)的前驅(qū)的康二,所以我們采用雙鏈表碳胳,這樣就將數(shù)據(jù)移動(dòng)的時(shí)間復(fù)雜度降低
    數(shù)據(jù)查找的時(shí)間復(fù)雜度: O(1)
    數(shù)據(jù)移動(dòng)的時(shí)間復(fù)雜度: O(1)
    空間復(fù)雜度: O(n)
    所以綜上所述,我們最后采用向鏈表+哈希表來實(shí)現(xiàn)這種算法

4. 代碼實(shí)現(xiàn)

1)首先我們先把雙鏈表的節(jié)點(diǎn)實(shí)現(xiàn)出來
我們申明一個(gè)類ZJMemeryCache沫勿,在ZJMemeryCache.m內(nèi)部實(shí)現(xiàn)_ZJLinkedMapNode
ZJMemeryCache.h代碼如下

@interface ZJMemeryCache : NSObject

@end

ZJMemeryCache.m代碼如下

#import "ZJMemeryCache.h"

//雙鏈表節(jié)點(diǎn)
@interface _ZJLinkedMapNode : NSObject {
    @package
    //指針域 前驅(qū)
    __unsafe_unretained _ZJLinkedMapNode *_prev;
    //指針域 后繼
    __unsafe_unretained _ZJLinkedMapNode *_next;
    //用于存哈希表的key
    id _key;
    //數(shù)據(jù)域 value
    id _value;
}
@end

@implementation _ZJLinkedMapNode
@end

@implementation ZJMemeryCache

@end

2)接下來我們再在ZJMemeryCache.m內(nèi)部實(shí)現(xiàn)雙鏈表_ZJLinkedMapNode
ZJMemeryCache.m代碼如下

//
//  ZJMemeryCache.m
//  ZJMemeryCache
//
//  Created by zhoujie on 2022/3/14.
//  Copyright ? 2022 ibireme. All rights reserved.
//

#import "ZJMemeryCache.h"

//雙鏈表節(jié)點(diǎn)
@interface _ZJLinkedMapNode : NSObject { ... }
@end

@implementation _ZJLinkedMapNode
@end

@interface _ZJLinkedMap : NSObject {
    @package
    //頭指針
    _ZJLinkedMapNode *_head;
    //尾指針,用于降低刪除尾節(jié)點(diǎn)的時(shí)間復(fù)雜度
    _ZJLinkedMapNode *_tail;
}
/// 若數(shù)據(jù)沒有在緩存中味混,將數(shù)據(jù)從頭插入产雹,代表最近最多使用
- (void)insertNodeAtHead:(_ZJLinkedMapNode *)node;
/// 若數(shù)據(jù)在緩存中,先將數(shù)據(jù)刪除翁锡,再從頭插入
- (void)bringNodeToHead:(_ZJLinkedMapNode *)node;
/// 刪除指定節(jié)點(diǎn)
- (void)removeNode:(_ZJLinkedMapNode *)node;
/// 移除尾節(jié)點(diǎn)蔓挖,代表緩存已滿,需要?jiǎng)h除最近最少使用的數(shù)據(jù)
- (_ZJLinkedMapNode *)removeTailNode;
@end

@implementation _ZJLinkedMap

- (instancetype)init {
    if (self = [super init]) {
        
    }
    return self;
}

- (void)dealloc {
}

- (void)insertNodeAtHead:(_ZJLinkedMapNode *)node {
}

- (void)bringNodeToHead:(_ZJLinkedMapNode *)node {
}

- (void)removeNode:(_ZJLinkedMapNode *)node {
}

- (_ZJLinkedMapNode *)removeTailNode {
    return nil;
}

@end

@implementation ZJMemeryCache

@end

我們先來實(shí)現(xiàn)- (void)insertNodeAtHead:(_ZJLinkedMapNode *)node;這個(gè)方法馆衔,這個(gè)方法對(duì)應(yīng)的是LRU運(yùn)行原理圖示中的第2瘟判、3、4步角溃,緩存中沒有數(shù)據(jù)拷获,直接緩存,實(shí)現(xiàn)代碼如下

/// 若數(shù)據(jù)沒有在緩存中减细,將數(shù)據(jù)從頭插入匆瓜,代表最近最多使用
- (void)insertNodeAtHead:(_ZJLinkedMapNode *)node {
    if (_head) {
        //鏈表不為空,從頭插入新節(jié)點(diǎn)
        node->_next = _head;
        _head->_prev = node;
        _head = node;
    }else {
        //鏈表為空
        _head = node;
        _tail = node;
    }
}

再來實(shí)現(xiàn)- (void)bringNodeToHead:(_ZJLinkedMapNode *)node;這個(gè)方法,這個(gè)方法對(duì)應(yīng)的圖中的第6步未蝌,先刪除對(duì)應(yīng)節(jié)點(diǎn)驮吱,再將刪除的節(jié)點(diǎn)從頭插入

/// 若數(shù)據(jù)在緩存中,先將數(shù)據(jù)刪除萧吠,再從頭插入
- (void)bringNodeToHead:(_ZJLinkedMapNode *)node {
    //如果命中的節(jié)點(diǎn)是頭節(jié)點(diǎn)左冬,則不用處理
    if (node == _head) {
        return;
    }
    //刪除命中的節(jié)點(diǎn)
    if (node == _tail) {
        _tail = _tail->_prev;
        _tail->_next = nil;
    }else {
        node->_prev->_next = node->_next;
        node->_next->_prev = node->_prev;
    }
    //從頭插入剛刪除的節(jié)點(diǎn)
    node->_next = _head;
    node->_prev = nil;
    _head->_prev = node;
    _head = node;
}

再實(shí)現(xiàn)- (void)removeNode:(_ZJLinkedMapNode *)node;這個(gè)方法

/// 刪除指定節(jié)點(diǎn)
- (void)removeNode:(_ZJLinkedMapNode *)node {
    if (node->_prev) {
        node->_prev->_next = node->_next;
    }
    if (node->_next) {
        node->_next->_prev = node->_prev;
    }
    if (node == _head) {
        _head = node->_next;
    }
    if (node == _tail) {
        _tail = node->_prev;
    }
}

最后再實(shí)現(xiàn)- (_ZJLinkedMapNode *)removeTailNode;這個(gè)方法,這個(gè)方法在緩存滿了之后調(diào)用纸型,刪除最近最少使用的數(shù)據(jù)拇砰,對(duì)應(yīng)的是圖中第5步的移除數(shù)據(jù)1

/// 移除尾節(jié)點(diǎn)九昧,代表緩存已滿,需要?jiǎng)h除最近最少使用的數(shù)據(jù)
- (_ZJLinkedMapNode *)removeTailNode {
    if (!_tail) {
        return nil;
    }
    _ZJLinkedMapNode *tail = _tail;
    if (_head == _tail) {
        _head = nil;
        _tail = nil;
    }else {
        _tail = _tail->_prev;
        _tail->_next = nil;
    }
    return tail;
}

至此雙向鏈表的主要功能算是完成了毕匀,我們再把哈希表的邏輯加入
3)加入哈希表邏輯
我們在_ZJLinkedMap中新增一個(gè)成員變量NSDictionary *_dic;

@interface _ZJLinkedMap : NSObject {
    @package
    //頭指針
    _ZJLinkedMapNode *_head;
    //尾指針铸鹰,用于降低刪除尾節(jié)點(diǎn)的時(shí)間復(fù)雜度
    _ZJLinkedMapNode *_tail;
    //哈希表,存儲(chǔ)節(jié)點(diǎn)
    NSMutableDictionary *_dic;
    //當(dāng)前容量
    NSInteger count;
}

_ZJLinkedMap中的init中初始化它

- (instancetype)init {
    if (self = [super init]) {
        _dic = [NSMutableDictionary dictionary];
    }
    return self;
}

然后在insertNodeAtHeadremoveNode皂岔、removeTailNode中添加增刪邏輯

- (void)insertNodeAtHead:(_ZJLinkedMapNode *)node {
    _dic[node->_key] = node->_value;
    count++;
    if (_head) {
        //鏈表不為空,從頭插入新節(jié)點(diǎn)
        node->_next = _head;
        _head->_prev = node;
        _head = node;
    }else {
        //鏈表為空
        _head = node;
        _tail = node;
    }
}

到這里蹋笼,我們雙向鏈表+哈希表的邏輯就算寫完了,我們在ZJMemeryCache.m中實(shí)現(xiàn)緩存功能
4)ZJMemeryCache實(shí)現(xiàn)緩存功能
我們申明兩個(gè)方法
取緩存:- (nullable id)objectForKey:(id)key;
存緩存:- (void)setObject:(nullable id)object forKey:(id)key;
代碼如下
ZJMemeryCache.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ZJMemeryCache : NSObject
// 緩存大小限制
@property NSUInteger countLimit;
// 取緩存
- (nullable id)objectForKey:(id)key;
// 存緩存
- (void)setObject:(nullable id)object forKey:(id)key;
@end

NS_ASSUME_NONNULL_END

ZJMemeryCache.m

//
//  ZJMemeryCache.m
//  ZJMemeryCache
//
//  Created by zhoujie on 2022/3/14.
//  Copyright ? 2022 ibireme. All rights reserved.
//

#import "ZJMemeryCache.h"
#import <pthread.h>

//雙鏈表節(jié)點(diǎn)
@interface _ZJLinkedMapNode : NSObject { ... }
@end

@implementation _ZJLinkedMapNode
@end

@interface _ZJLinkedMap : NSObject { ... }
@end

@implementation _ZJLinkedMap { ... }
@end


@implementation ZJMemeryCache {
    //添加線程鎖躁垛,保證ZJLinkedMap的線程安全
    pthread_mutex_t _lock;
    //lru(雙向鏈表+哈希表結(jié)構(gòu))
    _ZJLinkedMap *_lru;
}

- (instancetype)init {
    if (self = [super init]) {
        //初始化鎖
        pthread_mutex_init(&_lock, NULL);
        //初始化存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)
        _lru = [_ZJLinkedMap new];
        //初始化緩存大小
        _countLimit = 10;
    }
    return self;
}

// 取緩存
- (nullable id)objectForKey:(id)key {
     
}
// 存緩存
- (void)setObject:(nullable id)object forKey:(id)key {
    
}
@end

我們先來完成取緩存的操作- (nullable id)objectForKey:(id)key剖毯,取緩存很簡單,直接從緩存中查找教馆,找到將此數(shù)據(jù)移動(dòng)到最左側(cè)代表最近最多使用

// 取緩存
- (nullable id)objectForKey:(id)key {
    if (!key) return nil;
    //加鎖逊谋,保證線程安全
    pthread_mutex_lock(&_lock);
    //從緩存中查找數(shù)據(jù),如果找到數(shù)據(jù)土铺,將此數(shù)據(jù)變成最近最多使用胶滋,沒有直接返回空
    _ZJLinkedMapNode *node = _lru->_dic[@"key"];
    if (node) {
        [_lru bringNodeToHead:node];
    }
    pthread_mutex_unlock(&_lock);
    return node ? node->_value : nil;
}

再來處理存緩存操作

// 存緩存
- (void)setObject:(nullable id)object forKey:(id)key {
    if (!key) return;
    if (!object) return;
    //加鎖,保證線程安全
    pthread_mutex_lock(&_lock);
    //從緩存中查找數(shù)據(jù)悲敷,如果找到數(shù)據(jù)究恤,將此數(shù)據(jù)變成最近最多使用,沒有直接返回空
    _ZJLinkedMapNode *node = _lru->_dic[@"key"];
    if (node) {
        //如果能查到后德,直接移動(dòng)
        [_lru bringNodeToHead:node];
    }else {
        //不能查到,需要緩存
        //在緩存前需要判定緩存是否滿了
        if (_lru->count >= _countLimit) {
            //緩存滿了需要?jiǎng)h除最近最少使用的數(shù)據(jù)
            [_lru removeTailNode];
        }
        //添加最近最多使用的數(shù)據(jù)
        node = [_ZJLinkedMapNode new];
        node->_key = key;
        node->_value = object;
        [_lru insertNodeAtHead:node];
    }
    pthread_mutex_unlock(&_lock);
}

到這里YYMemeryCache里的LRU功能就算寫完了部宿,最后附上YYMemeryCache里的代碼和我們寫的代碼對(duì)比

ZJMemeryCache
//
//  ZJMemeryCache.m
//  ZJMemeryCache
//
//  Created by zhoujie on 2022/3/14.
//  Copyright ? 2022 ibireme. All rights reserved.
//

#import "ZJMemeryCache.h"
#import <pthread.h>

//雙鏈表節(jié)點(diǎn)
@interface _ZJLinkedMapNode : NSObject {
    @package
    //指針域 前驅(qū)
    __unsafe_unretained _ZJLinkedMapNode *_prev;
    //指針域 后繼
    __unsafe_unretained _ZJLinkedMapNode *_next;
    //用于存哈希表的key
    id _key;
    //數(shù)據(jù)域 value
    id _value;
}
@end

@implementation _ZJLinkedMapNode
@end

@interface _ZJLinkedMap : NSObject {
    @package
    //頭指針
    _ZJLinkedMapNode *_head;
    //尾指針,用于降低刪除尾節(jié)點(diǎn)的時(shí)間復(fù)雜度
    _ZJLinkedMapNode *_tail;
    //哈希表瓢湃,存儲(chǔ)節(jié)點(diǎn)
    NSMutableDictionary *_dic;
    //當(dāng)前容量
    NSInteger count;
}
/// 若數(shù)據(jù)沒有在緩存中理张,將數(shù)據(jù)從頭插入,代表最近最多使用
- (void)insertNodeAtHead:(_ZJLinkedMapNode *)node;
/// 若數(shù)據(jù)在緩存中绵患,先將數(shù)據(jù)刪除雾叭,再從頭插入
- (void)bringNodeToHead:(_ZJLinkedMapNode *)node;
/// 刪除指定節(jié)點(diǎn)
- (void)removeNode:(_ZJLinkedMapNode *)node;
/// 移除尾節(jié)點(diǎn),代表緩存已滿藏雏,需要?jiǎng)h除最近最少使用的數(shù)據(jù)
- (_ZJLinkedMapNode *)removeTailNode;
@end

@implementation _ZJLinkedMap

- (instancetype)init {
    if (self = [super init]) {
        _dic = [NSMutableDictionary dictionary];
    }
    return self;
}

- (void)dealloc {
}

- (void)insertNodeAtHead:(_ZJLinkedMapNode *)node {
    _dic[node->_key] = node->_value;
    count++;
    if (_head) {
        //鏈表不為空,從頭插入新節(jié)點(diǎn)
        node->_next = _head;
        _head->_prev = node;
        _head = node;
    }else {
        //鏈表為空
        _head = node;
        _tail = node;
    }
}

- (void)bringNodeToHead:(_ZJLinkedMapNode *)node {
    //如果命中的節(jié)點(diǎn)是頭節(jié)點(diǎn)拷况,則不用處理
    if (node == _head) {
        return;
    }
    //刪除命中的節(jié)點(diǎn)
    if (node == _tail) {
        _tail = _tail->_prev;
        _tail->_next = nil;
    }else {
        node->_prev->_next = node->_next;
        node->_next->_prev = node->_prev;
    }
    //從頭插入剛刪除的節(jié)點(diǎn)
    node->_next = _head;
    node->_prev = nil;
    _head->_prev = node;
    _head = node;
}

- (void)removeNode:(_ZJLinkedMapNode *)node {
    [_dic removeObjectForKey:node->_key];
    count--;
    if (node->_prev) {
        node->_prev->_next = node->_next;
    }
    if (node->_next) {
        node->_next->_prev = node->_prev;
    }
    if (node == _head) {
        _head = node->_next;
    }
    if (node == _tail) {
        _tail = node->_prev;
    }
}

/// 移除尾節(jié)點(diǎn),代表緩存已滿掘殴,需要?jiǎng)h除最近最少使用的數(shù)據(jù)
- (_ZJLinkedMapNode *)removeTailNode {
    if (!_tail) {
        return nil;
    }
    _ZJLinkedMapNode *tail = _tail;
    [_dic removeObjectForKey:tail->_key];
    count--;
    if (_head == _tail) {
        _head = nil;
        _tail = nil;
    }else {
        _tail = _tail->_prev;
        _tail->_next = nil;
    }
    return tail;
}

@end


@implementation ZJMemeryCache {
    //添加線程鎖赚瘦,保證ZJLinkedMap的線程安全
    pthread_mutex_t _lock;
    //lru(雙向鏈表+哈希表結(jié)構(gòu))
    _ZJLinkedMap *_lru;
}

- (instancetype)init {
    if (self = [super init]) {
        //初始化鎖
        pthread_mutex_init(&_lock, NULL);
        //初始化存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)
        _lru = [_ZJLinkedMap new];
        //初始化緩存大小
        _countLimit = 10;
    }
    return self;
}

// 取緩存
- (nullable id)objectForKey:(id)key {
    if (!key) return nil;
    //加鎖,保證線程安全
    pthread_mutex_lock(&_lock);
    //從緩存中查找數(shù)據(jù)奏寨,如果找到數(shù)據(jù)起意,將此數(shù)據(jù)變成最近最多使用,沒有直接返回空
    _ZJLinkedMapNode *node = _lru->_dic[@"key"];
    if (node) {
        [_lru bringNodeToHead:node];
    }
    pthread_mutex_unlock(&_lock);
    return node ? node->_value : nil;
}

// 存緩存
- (void)setObject:(nullable id)object forKey:(id)key {
    if (!key) return;
    if (!object) return;
    //加鎖病瞳,保證線程安全
    pthread_mutex_lock(&_lock);
    //從緩存中查找數(shù)據(jù)揽咕,如果找到數(shù)據(jù)悲酷,將此數(shù)據(jù)變成最近最多使用,沒有直接返回空
    _ZJLinkedMapNode *node = _lru->_dic[@"key"];
    if (node) {
        //如果能查到亲善,直接移動(dòng)
        [_lru bringNodeToHead:node];
    }else {
        //不能查到,需要緩存
        //在緩存前需要判定緩存是否滿了
        if (_lru->count >= _countLimit) {
            //緩存滿了需要?jiǎng)h除最近最少使用的數(shù)據(jù)
            [_lru removeTailNode];
        }
        //添加最近最多使用的數(shù)據(jù)
        node = [_ZJLinkedMapNode new];
        node->_key = key;
        node->_value = object;
        [_lru insertNodeAtHead:node];
    }
    pthread_mutex_unlock(&_lock);
}
@end

YYMemeryCache
//
//  YYMemoryCache.m
//  YYCache <https://github.com/ibireme/YYCache>
//
//  Created by ibireme on 15/2/7.
//  Copyright (c) 2015 ibireme.
//
//  This source code is licensed under the MIT-style license found in the
//  LICENSE file in the root directory of this source tree.
//

#import "YYMemoryCache.h"
#import <UIKit/UIKit.h>
#import <CoreFoundation/CoreFoundation.h>
#import <QuartzCore/QuartzCore.h>
#import <pthread.h>


static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() {
    return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
}

/**
 A node in linked map.
 Typically, you should not use this class directly.
 */
@interface _YYLinkedMapNode : NSObject {
    @package
    __unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
    __unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
    id _key;
    id _value;
    NSUInteger _cost;
    NSTimeInterval _time;
}
@end

@implementation _YYLinkedMapNode
@end


/**
 A linked map used by YYMemoryCache.
 It's not thread-safe and does not validate the parameters.
 
 Typically, you should not use this class directly.
 */
@interface _YYLinkedMap : NSObject {
    @package
    CFMutableDictionaryRef _dic; // do not set object directly
    NSUInteger _totalCost;
    NSUInteger _totalCount;
    _YYLinkedMapNode *_head; // MRU, do not change it directly
    _YYLinkedMapNode *_tail; // LRU, do not change it directly
    BOOL _releaseOnMainThread;
    BOOL _releaseAsynchronously;
}

/// Insert a node at head and update the total cost.
/// Node and node.key should not be nil.
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;

/// Bring a inner node to header.
/// Node should already inside the dic.
- (void)bringNodeToHead:(_YYLinkedMapNode *)node;

/// Remove a inner node and update the total cost.
/// Node should already inside the dic.
- (void)removeNode:(_YYLinkedMapNode *)node;

/// Remove tail node if exist.
- (_YYLinkedMapNode *)removeTailNode;

/// Remove all node in background queue.
- (void)removeAll;

@end

@implementation _YYLinkedMap

- (instancetype)init {
    self = [super init];
    _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    _releaseOnMainThread = NO;
    _releaseAsynchronously = YES;
    return self;
}

- (void)dealloc {
    CFRelease(_dic);
}

- (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
    CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
    _totalCost += node->_cost;
    _totalCount++;
    if (_head) {
        node->_next = _head;
        _head->_prev = node;
        _head = node;
    } else {
        _head = _tail = node;
    }
}

- (void)bringNodeToHead:(_YYLinkedMapNode *)node {
    if (_head == node) return;
    
    if (_tail == node) {
        _tail = node->_prev;
        _tail->_next = nil;
    } else {
        node->_next->_prev = node->_prev;
        node->_prev->_next = node->_next;
    }
    node->_next = _head;
    node->_prev = nil;
    _head->_prev = node;
    _head = node;
}

- (void)removeNode:(_YYLinkedMapNode *)node {
    CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
    _totalCost -= node->_cost;
    _totalCount--;
    if (node->_next) node->_next->_prev = node->_prev;
    if (node->_prev) node->_prev->_next = node->_next;
    if (_head == node) _head = node->_next;
    if (_tail == node) _tail = node->_prev;
}

- (_YYLinkedMapNode *)removeTailNode {
    if (!_tail) return nil;
    _YYLinkedMapNode *tail = _tail;
    CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));
    _totalCost -= _tail->_cost;
    _totalCount--;
    if (_head == _tail) {
        _head = _tail = nil;
    } else {
        _tail = _tail->_prev;
        _tail->_next = nil;
    }
    return tail;
}

- (void)removeAll {
    _totalCost = 0;
    _totalCount = 0;
    _head = nil;
    _tail = nil;
    if (CFDictionaryGetCount(_dic) > 0) {
        CFMutableDictionaryRef holder = _dic;
        _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        
        if (_releaseAsynchronously) {
            dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
            dispatch_async(queue, ^{
                CFRelease(holder); // hold and release in specified queue
            });
        } else if (_releaseOnMainThread && !pthread_main_np()) {
            dispatch_async(dispatch_get_main_queue(), ^{
                CFRelease(holder); // hold and release in specified queue
            });
        } else {
            CFRelease(holder);
        }
    }
}

@end



@implementation YYMemoryCache {
    pthread_mutex_t _lock;
    _YYLinkedMap *_lru;
    dispatch_queue_t _queue;
}

- (void)_trimRecursively {
    __weak typeof(self) _self = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        [self _trimInBackground];
        [self _trimRecursively];
    });
}

- (void)_trimInBackground {
    dispatch_async(_queue, ^{
        [self _trimToCost:self->_costLimit];
        [self _trimToCount:self->_countLimit];
        [self _trimToAge:self->_ageLimit];
    });
}

- (void)_trimToCost:(NSUInteger)costLimit {
    BOOL finish = NO;
    pthread_mutex_lock(&_lock);
    if (costLimit == 0) {
        [_lru removeAll];
        finish = YES;
    } else if (_lru->_totalCost <= costLimit) {
        finish = YES;
    }
    pthread_mutex_unlock(&_lock);
    if (finish) return;
    
    NSMutableArray *holder = [NSMutableArray new];
    while (!finish) {
        if (pthread_mutex_trylock(&_lock) == 0) {
            if (_lru->_totalCost > costLimit) {
                _YYLinkedMapNode *node = [_lru removeTailNode];
                if (node) [holder addObject:node];
            } else {
                finish = YES;
            }
            pthread_mutex_unlock(&_lock);
        } else {
            usleep(10 * 1000); //10 ms
        }
    }
    if (holder.count) {
        dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
        dispatch_async(queue, ^{
            [holder count]; // release in queue
        });
    }
}

- (void)_trimToCount:(NSUInteger)countLimit {
    BOOL finish = NO;
    pthread_mutex_lock(&_lock);
    if (countLimit == 0) {
        [_lru removeAll];
        finish = YES;
    } else if (_lru->_totalCount <= countLimit) {
        finish = YES;
    }
    pthread_mutex_unlock(&_lock);
    if (finish) return;
    
    NSMutableArray *holder = [NSMutableArray new];
    while (!finish) {
        if (pthread_mutex_trylock(&_lock) == 0) {
            if (_lru->_totalCount > countLimit) {
                _YYLinkedMapNode *node = [_lru removeTailNode];
                if (node) [holder addObject:node];
            } else {
                finish = YES;
            }
            pthread_mutex_unlock(&_lock);
        } else {
            usleep(10 * 1000); //10 ms
        }
    }
    if (holder.count) {
        dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
        dispatch_async(queue, ^{
            [holder count]; // release in queue
        });
    }
}

- (void)_trimToAge:(NSTimeInterval)ageLimit {
    BOOL finish = NO;
    NSTimeInterval now = CACurrentMediaTime();
    pthread_mutex_lock(&_lock);
    if (ageLimit <= 0) {
        [_lru removeAll];
        finish = YES;
    } else if (!_lru->_tail || (now - _lru->_tail->_time) <= ageLimit) {
        finish = YES;
    }
    pthread_mutex_unlock(&_lock);
    if (finish) return;
    
    NSMutableArray *holder = [NSMutableArray new];
    while (!finish) {
        if (pthread_mutex_trylock(&_lock) == 0) {
            if (_lru->_tail && (now - _lru->_tail->_time) > ageLimit) {
                _YYLinkedMapNode *node = [_lru removeTailNode];
                if (node) [holder addObject:node];
            } else {
                finish = YES;
            }
            pthread_mutex_unlock(&_lock);
        } else {
            usleep(10 * 1000); //10 ms
        }
    }
    if (holder.count) {
        dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
        dispatch_async(queue, ^{
            [holder count]; // release in queue
        });
    }
}

- (void)_appDidReceiveMemoryWarningNotification {
    if (self.didReceiveMemoryWarningBlock) {
        self.didReceiveMemoryWarningBlock(self);
    }
    if (self.shouldRemoveAllObjectsOnMemoryWarning) {
        [self removeAllObjects];
    }
}

- (void)_appDidEnterBackgroundNotification {
    if (self.didEnterBackgroundBlock) {
        self.didEnterBackgroundBlock(self);
    }
    if (self.shouldRemoveAllObjectsWhenEnteringBackground) {
        [self removeAllObjects];
    }
}

#pragma mark - public

- (instancetype)init {
    self = super.init;
    pthread_mutex_init(&_lock, NULL);
    _lru = [_YYLinkedMap new];
    _queue = dispatch_queue_create("com.ibireme.cache.memory", DISPATCH_QUEUE_SERIAL);
    
    _countLimit = NSUIntegerMax;
    _costLimit = NSUIntegerMax;
    _ageLimit = DBL_MAX;
    _autoTrimInterval = 5.0;
    _shouldRemoveAllObjectsOnMemoryWarning = YES;
    _shouldRemoveAllObjectsWhenEnteringBackground = YES;
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil];
    
    [self _trimRecursively];
    return self;
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
    [_lru removeAll];
    pthread_mutex_destroy(&_lock);
}

- (NSUInteger)totalCount {
    pthread_mutex_lock(&_lock);
    NSUInteger count = _lru->_totalCount;
    pthread_mutex_unlock(&_lock);
    return count;
}

- (NSUInteger)totalCost {
    pthread_mutex_lock(&_lock);
    NSUInteger totalCost = _lru->_totalCost;
    pthread_mutex_unlock(&_lock);
    return totalCost;
}

- (BOOL)releaseOnMainThread {
    pthread_mutex_lock(&_lock);
    BOOL releaseOnMainThread = _lru->_releaseOnMainThread;
    pthread_mutex_unlock(&_lock);
    return releaseOnMainThread;
}

- (void)setReleaseOnMainThread:(BOOL)releaseOnMainThread {
    pthread_mutex_lock(&_lock);
    _lru->_releaseOnMainThread = releaseOnMainThread;
    pthread_mutex_unlock(&_lock);
}

- (BOOL)releaseAsynchronously {
    pthread_mutex_lock(&_lock);
    BOOL releaseAsynchronously = _lru->_releaseAsynchronously;
    pthread_mutex_unlock(&_lock);
    return releaseAsynchronously;
}

- (void)setReleaseAsynchronously:(BOOL)releaseAsynchronously {
    pthread_mutex_lock(&_lock);
    _lru->_releaseAsynchronously = releaseAsynchronously;
    pthread_mutex_unlock(&_lock);
}

- (BOOL)containsObjectForKey:(id)key {
    if (!key) return NO;
    pthread_mutex_lock(&_lock);
    BOOL contains = CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key));
    pthread_mutex_unlock(&_lock);
    return contains;
}

- (id)objectForKey:(id)key {
    if (!key) return nil;
    pthread_mutex_lock(&_lock);
    _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
    if (node) {
        node->_time = CACurrentMediaTime();
        [_lru bringNodeToHead:node];
    }
    pthread_mutex_unlock(&_lock);
    return node ? node->_value : nil;
}

- (void)setObject:(id)object forKey:(id)key {
    [self setObject:object forKey:key withCost:0];
}

- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
    if (!key) return;
    if (!object) {
        [self removeObjectForKey:key];
        return;
    }
    pthread_mutex_lock(&_lock);
    _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
    NSTimeInterval now = CACurrentMediaTime();
    if (node) {
        _lru->_totalCost -= node->_cost;
        _lru->_totalCost += cost;
        node->_cost = cost;
        node->_time = now;
        node->_value = object;
        [_lru bringNodeToHead:node];
    } else {
        node = [_YYLinkedMapNode new];
        node->_cost = cost;
        node->_time = now;
        node->_key = key;
        node->_value = object;
        [_lru insertNodeAtHead:node];
    }
    if (_lru->_totalCost > _costLimit) {
        dispatch_async(_queue, ^{
            [self trimToCost:_costLimit];
        });
    }
    if (_lru->_totalCount > _countLimit) {
        _YYLinkedMapNode *node = [_lru removeTailNode];
        if (_lru->_releaseAsynchronously) {
            dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
            dispatch_async(queue, ^{
                [node class]; //hold and release in queue
            });
        } else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [node class]; //hold and release in queue
            });
        }
    }
    pthread_mutex_unlock(&_lock);
}

- (void)removeObjectForKey:(id)key {
    if (!key) return;
    pthread_mutex_lock(&_lock);
    _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
    if (node) {
        [_lru removeNode:node];
        if (_lru->_releaseAsynchronously) {
            dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
            dispatch_async(queue, ^{
                [node class]; //hold and release in queue
            });
        } else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [node class]; //hold and release in queue
            });
        }
    }
    pthread_mutex_unlock(&_lock);
}

- (void)removeAllObjects {
    pthread_mutex_lock(&_lock);
    [_lru removeAll];
    pthread_mutex_unlock(&_lock);
}

- (void)trimToCount:(NSUInteger)count {
    if (count == 0) {
        [self removeAllObjects];
        return;
    }
    [self _trimToCount:count];
}

- (void)trimToCost:(NSUInteger)cost {
    [self _trimToCost:cost];
}

- (void)trimToAge:(NSTimeInterval)age {
    [self _trimToAge:age];
}

- (NSString *)description {
    if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _name];
    else return [NSString stringWithFormat:@"<%@: %p>", self.class, self];
}

@end

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末设易,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蛹头,更是在濱河造成了極大的恐慌顿肺,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渣蜗,死亡現(xiàn)場離奇詭異屠尊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)耕拷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門讼昆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人骚烧,你說我怎么就攤上這事浸赫。” “怎么了止潘?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵掺炭,是天一觀的道長。 經(jīng)常有香客問我凭戴,道長,這世上最難降的妖魔是什么炕矮? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任么夫,我火速辦了婚禮,結(jié)果婚禮上肤视,老公的妹妹穿的比我還像新娘档痪。我一直安慰自己,他們只是感情好邢滑,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布腐螟。 她就那樣靜靜地躺著,像睡著了一般困后。 火紅的嫁衣襯著肌膚如雪乐纸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天摇予,我揣著相機(jī)與錄音汽绢,去河邊找鬼。 笑死侧戴,一個(gè)胖子當(dāng)著我的面吹牛宁昭,可吹牛的內(nèi)容都是我干的跌宛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼积仗,長吁一口氣:“原來是場噩夢啊……” “哼疆拘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起寂曹,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤哎迄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后稀颁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芬失,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年匾灶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了棱烂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阶女,死狀恐怖颊糜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情秃踩,我是刑警寧澤衬鱼,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站憔杨,受9級(jí)特大地震影響鸟赫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜消别,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一抛蚤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寻狂,春花似錦岁经、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至纠亚,卻和暖如春塘慕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背菜枷。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工苍糠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人啤誊。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓岳瞭,卻偏偏與公主長得像拥娄,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瞳筏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 讀完本文稚瘾,你可以去力扣拿下如下題目: 146.LRU緩存機(jī)制[https://leetcode-cn.com/pr...
    labuladong閱讀 948評(píng)論 1 5
  • LRU就是一種緩存淘汰策略。 計(jì)算機(jī)的緩存容量有限姚炕,如果緩存滿了就要?jiǎng)h除一些內(nèi)容摊欠,給新內(nèi)容騰位置。但問題是柱宦,刪除哪...
    隨風(fēng)_d6a2閱讀 533評(píng)論 0 2
  • 一些椒、什么是 LRU 算法 LRU就是一種緩存淘汰策略。 計(jì)算機(jī)的緩存容量有限掸刊,如果緩存滿了就要?jiǎng)h除一些內(nèi)容免糕,給新內(nèi)...
    Minority閱讀 2,314評(píng)論 0 3
  • 手寫LRU緩存淘汰算法 背景 在我們這個(gè)日益追求高效的世界,我們對(duì)任何事情的等待都顯得十分的浮躁忧侧,網(wǎng)頁頁面刷新不出...
    Simon郎閱讀 707評(píng)論 1 1
  • 寫在前 就是一種緩存淘汰策略石窑。 計(jì)算機(jī)的緩存容量有限,如果緩存滿了就要?jiǎng)h除一些內(nèi)容蚓炬,給新內(nèi)容騰位置松逊。但問題是,刪除...
    _code_x閱讀 1,212評(píng)論 0 7