源碼解析--YYCache

封面.jpg

前言:準(zhǔn)備看下YY系列中的YYWebImage框架,發(fā)現(xiàn)該框架是使用YYCache來做緩存的土至。那就從緩存開始吧.
先奉上YYCache框架的地址以及作者的設(shè)計(jì)思路
學(xué)習(xí)YYCache框架你可以get到:
1.優(yōu)雅的代碼風(fēng)格
2.優(yōu)秀的接口設(shè)計(jì)
3.YYCache的層次結(jié)構(gòu)
4.YYMemoryCache類的層次結(jié)構(gòu)和緩存機(jī)制
5.YYDiskCache類的層次結(jié)構(gòu)和緩存機(jī)制

YYCache

YYCache結(jié)構(gòu).png

YYCache最為食物鏈的最頂端的男人,并沒有什么好說的猾昆,所以我們就從YYMemoryCacheYYDiskCache開始吧陶因。

YYMemoryCache

YYMemoryCache內(nèi)存儲存是的原理是利用CFDictionary對象的 key-value開辟內(nèi)存儲存機(jī)制和雙向鏈表原理來實(shí)現(xiàn)LRU算法。這里是官方文檔對CFDictionary的解釋:

CFMutableDictionary creates dynamic dictionaries where you can add or delete key-value pairs at any time, and the dictionary automatically allocates memory as needed.

YYMemoryCache類結(jié)構(gòu)圖.png

YYMemoryCache初始化的時候會建立空的私有對象YYLinkedMap鏈表垂蜗,接下來所有的操作其實(shí)就是對這個鏈表的操作坑赡。當(dāng)然,YYMemoryCache提供了一個定時器接口給你么抗,你可以通過設(shè)置autoTrimInterval屬性去完成每隔一定時間去檢查countLimit毅否,costLimit是否達(dá)到了最大限制,并做相應(yīng)的操作蝇刀。

- (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];
        //遞歸的調(diào)用
        [self _trimRecursively];
    });
}

- (void)_trimInBackground {
    dispatch_async(_queue, ^{
        //檢查是否達(dá)到設(shè)置的最大消耗,并做相應(yīng)的處理
        [self _trimToCost:self->_costLimit];
        //檢查是否達(dá)到該緩存設(shè)置的最大持有對象數(shù),并做相應(yīng)的處理
        [self _trimToCount:self->_countLimit];
        //當(dāng)前的時間和鏈表最后的節(jié)點(diǎn)時間的差值是否大于設(shè)定的_ageLimit值螟加,移除大于該值得節(jié)點(diǎn)
        [self _trimToAge:self->_ageLimit];
    });
}

YYMemoryCache以block的形式給你提供了下面接口:

  • didReceiveMemoryWarningBlock(當(dāng)app接受到內(nèi)存警告)
  • didEnterBackgroundBlock (當(dāng)app進(jìn)入到后臺)

當(dāng)然,你也可以通過設(shè)置相應(yīng)的shouldRemoveAllObjectsOnMemoryWarningshouldRemoveAllObjectsWhenEnteringBackground值來移除YYMemoryCache持有的鏈表吞琐。

下面我們來看看YYMemoryCache類的增捆探,刪,查等操作站粟。在這之前我們先看看YYLinkedMap這個類黍图。

1.YYLinkedMap內(nèi)部結(jié)構(gòu)

YYLinkedMap作為雙向鏈表,主要的工作是為YYMemoryCache類提供對YYLinkedMapNode節(jié)點(diǎn)的操作奴烙。下圖綠色部分代表節(jié)點(diǎn):

雙向鏈表結(jié)構(gòu).png

下圖是鏈表節(jié)點(diǎn)的結(jié)構(gòu)圖:
鏈表節(jié)點(diǎn).png

現(xiàn)在我們先來看如何去構(gòu)造一個鏈表添加節(jié)點(diǎn):
setObject.png

- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
    if (!key) return;
    if (!object) {
        [self removeObjectForKey:key];
        return;
    }
    //鎖
    pthread_mutex_lock(&_lock);
    //查找是否存在對應(yīng)該key的節(jié)點(diǎn)
    _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
    NSTimeInterval now = CACurrentMediaTime();
    if (node) {
        //修改相應(yīng)的數(shù)據(jù)
        _lru->_totalCost -= node->_cost;
        _lru->_totalCost += cost;
        node->_cost = cost;
        node->_time = now;
        node->_value = object;
        //根據(jù)LRU算法原理助被,將訪問的點(diǎn)移到最前面
        [_lru bringNodeToHead:node];
    } else {
        node = [_YYLinkedMapNode new];
        node->_cost = cost;
        node->_time = now;
        node->_key = key;
        node->_value = object;
        //在鏈表最前面插入結(jié)點(diǎn)
        [_lru insertNodeAtHead:node];
    }
    //判斷鏈表的消耗的總資源是否大于設(shè)置的最大值
    if (_lru->_totalCost > _costLimit) {
        dispatch_async(_queue, ^{
            [self trimToCost:_costLimit];
        });
    }
    //判斷鏈表的總持有節(jié)點(diǎn)是否大于該緩存設(shè)置的最大持有數(shù)
    if (_lru->_totalCount > _countLimit) {  //當(dāng)超出設(shè)定的最大的值
        //移除鏈表最后的節(jié)點(diǎn)
        _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);
}

你可以點(diǎn)擊這里自己去操作雙向鏈表

addNode.gif

鏈表移除節(jié)點(diǎn)的操作:

- (void)removeObjectForKey:(id)key {
    if (!key) return;
    //鎖
    pthread_mutex_lock(&_lock);
    //根據(jù)key拿到相應(yīng)的節(jié)點(diǎn)
    _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
    if (node) {
        [_lru removeNode:node];
        //決定在哪個隊(duì)列里做釋放操作
        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);
}

removeNode.gif

YYMemoryCache類還為我們提供了下列接口方便我們調(diào)用:

- (BOOL)containsObjectForKey:(id)key;
- (nullable id)objectForKey:(id)key;
- (void)removeAllObjects;

總結(jié):YYMemoryCache是利用key-value機(jī)制內(nèi)存緩存類剖张,所有的方法都是線程安全的。如果你熟悉NSCache類揩环,你會發(fā)現(xiàn)兩者的接口很是相似搔弄。
當(dāng)然YYMemoryCache有著自己的特點(diǎn):
1.YYMemoryCache采用LRU(least-recently-used)算法來移除節(jié)點(diǎn)。
2.YYMemoryCache可以用countLimit丰滑,costLimit顾犹,ageLimit屬性做相應(yīng)的控制。
3.YYMemoryCache類可以設(shè)置相應(yīng)的屬性來控制退到后臺或者接受到內(nèi)存警告的時候移除鏈表褒墨。

YYKVStorage

YYKVStorage是一個基于sql數(shù)據(jù)庫和文件寫入的緩存類炫刷,注意它并不是線程安全。你可以自己定義YYKVStorageType來確定是那種寫入方式:

typedef NS_ENUM(NSUInteger, YYKVStorageType) {
    
    /// The `value` is stored as a file in file system.
    YYKVStorageTypeFile = 0,
    
    /// The `value` is stored in sqlite with blob type.
    YYKVStorageTypeSQLite = 1,
    
    /// The `value` is stored in file system or sqlite based on your choice.
    YYKVStorageTypeMixed = 2,
};

1.寫入和更新

我們看看Demo中直接用YYKVStorage儲存NSNumber和NSData YYKVStorageTypeFileYYKVStorageTypeSQLite類型所用的時間:

7.png

你可以發(fā)現(xiàn)在儲存小型數(shù)據(jù)NSNumberYYKVStorageTypeFile類型是YYKVStorageTypeSQLite大約4倍多郁妈,而在大型數(shù)據(jù)的時候兩者的表現(xiàn)是相反的柬唯。顯然選擇合適的儲存方式是很有必要的。
這里需要提醒的事:

  • DemoYYKVStorageTypeFile類型其實(shí)不僅寫入了本地文件也同時寫入了數(shù)據(jù)庫圃庭,只不過數(shù)據(jù)庫里面存的是除了value值以外的key, filename, size, inline_data(NULL), modification_time , last_access_time, extended_data字段锄奢。
- (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value filename:(NSString *)filename extendedData:(NSData *)extendedData {
    if (key.length == 0 || value.length == 0) return NO;
    //_type為YYKVStorageTypeSQLite時候filename應(yīng)該為空球拦,不然還是會寫入文件
    //_type為YYKVStorageTypeFile時候filename的值不能為空
    if (_type == YYKVStorageTypeFile && filename.length == 0) {
        return NO;
    }
    //是否寫入文件是根據(jù)filename.length長度來判斷的
    if (filename.length) {
        //先儲存在文件里面
        if (![self _fileWriteWithName:filename data:value]) {
            return NO;
        }
        //儲存在sql數(shù)據(jù)庫
        if (![self _dbSaveWithKey:key value:value fileName:filename extendedData:extendedData]) {
            //儲存數(shù)據(jù)庫失敗就刪除之前儲存的文件
            [self _fileDeleteWithName:filename];
            return NO;
        }
        return YES;
    } else {
        if (_type != YYKVStorageTypeSQLite) {
            NSString *filename = [self _dbGetFilenameWithKey:key];
            if (filename) {
                [self _fileDeleteWithName:filename];
            }
        }
        //儲存在sql數(shù)據(jù)庫
        return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData];
    }
}

插入或者是更新數(shù)據(jù)庫

- (BOOL)_dbSaveWithKey:(NSString *)key value:(NSData *)value fileName:(NSString *)fileName extendedData:(NSData *)extendedData {
    NSString *sql = @"insert or replace into manifest (key, filename, size, inline_data, modification_time, last_access_time, extended_data) values (?1, ?2, ?3, ?4, ?5, ?6, ?7);";
    sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
    if (!stmt) return NO;
    
    int timestamp = (int)time(NULL);
    //sqlite3_bind_xxx函數(shù)給這條語句綁定參數(shù)
    sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
    sqlite3_bind_text(stmt, 2, fileName.UTF8String, -1, NULL);
    sqlite3_bind_int(stmt, 3, (int)value.length);
    //當(dāng)fileName為空的時候存在數(shù)據(jù)庫的是value.bytes本昏,不然存的是NULl對象
    if (fileName.length == 0) {
        sqlite3_bind_blob(stmt, 4, value.bytes, (int)value.length, 0);
    } else {
        sqlite3_bind_blob(stmt, 4, NULL, 0, 0);
    }
    sqlite3_bind_int(stmt, 5, timestamp);
    sqlite3_bind_int(stmt, 6, timestamp);
    sqlite3_bind_blob(stmt, 7, extendedData.bytes, (int)extendedData.length, 0);
    //通過sqlite3_step命令執(zhí)行創(chuàng)建表的語句
    int result = sqlite3_step(stmt);
    if (result != SQLITE_DONE) {
        if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite insert error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
        return NO;
    }
    return YES;
}

2.讀取

我們嘗試的去緩存里面拿取數(shù)據(jù)卜录,我們發(fā)現(xiàn)當(dāng)為YYKVStorage對象type不同恕曲,存取的方式不同所以讀取的方式也不同:
1.因?yàn)樵诓迦氲臅r候我們就說了伊诵,當(dāng)為YYKVStorageTypeFile類型的時候數(shù)據(jù)是存在本地文件的其他存在數(shù)據(jù)庫墓陈。所以YYKVStorage對象先根據(jù)key從數(shù)據(jù)庫拿到數(shù)據(jù)然后包裝成YYKVStorageItem對象呆奕,然后再根據(jù)filename讀取本地文件數(shù)據(jù)賦給YYKVStorageItem對象的value屬性梢杭。
2.當(dāng)為YYKVStorageTypeSQLite類型就是直接從數(shù)據(jù)庫把所有數(shù)據(jù)都讀出來賦給YYKVStorageItem對象儒旬。

- (YYKVStorageItem *)getItemForKey:(NSString *)key {
    if (key.length == 0) return nil;
    /*先從數(shù)據(jù)庫讀包裝item栏账,
     當(dāng)時filename不為空的時候,以為著數(shù)據(jù)庫里面沒有存Value值栈源,還得去文件里面讀出來value值
     當(dāng)時filename為空的時候挡爵,意味著直接從數(shù)據(jù)庫來拿取Value值
     */
    YYKVStorageItem *item = [self _dbGetItemWithKey:key excludeInlineData:NO];
    if (item) {
        //更新的last_access_time字段
        [self _dbUpdateAccessTimeWithKey:key];
        if (item.filename) {
            //從文件里面讀取value值
            item.value = [self _fileReadWithName:item.filename];
            if (!item.value) {
                //數(shù)據(jù)為空則從數(shù)據(jù)庫刪除這條記錄
                [self _dbDeleteItemWithKey:key];
                item = nil;
            }
        }
    }
    return item;
}

3.刪除

YYKVStorage的type當(dāng)為YYKVStorageTypeFile類型是根據(jù)key將本地和數(shù)據(jù)庫都刪掉,而YYKVStorageTypeSQLite是根據(jù)key刪除掉數(shù)據(jù)庫就好了。

- (BOOL)removeItemForKey:(NSString *)key {
    if (key.length == 0) return NO;
    switch (_type) {
        case YYKVStorageTypeSQLite: {
            return [self _dbDeleteItemWithKey:key];
        } break;
        case YYKVStorageTypeFile:
        case YYKVStorageTypeMixed: {
            NSString *filename = [self _dbGetFilenameWithKey:key];
            if (filename) {
                [self _fileDeleteWithName:filename];
            }
            return [self _dbDeleteItemWithKey:key];
        } break;
        default: return NO;
    }
}

我們這里分別列取了增刪改查的單個key的操作甚垦,你還可以去批量的去操作key的數(shù)組茶鹃。但是其實(shí)都大同小異的流程,就不一一累述了艰亮。上個圖吧:

屏幕快照 2016-12-28 下午10.10.38.png

這個類也就看的差不多了闭翩,但是要注意的事,YYCache作者并不希望我們直接使用這個類迄埃,而是使用更高層的YYDiskCache類疗韵。那我們就繼續(xù)往下面看吧。

YYDiskCache

YYDiskCache類有兩種初始化方式:

- (nullable instancetype)initWithPath:(NSString *)path;
- (nullable instancetype)initWithPath:(NSString *)path
                      inlineThreshold:(NSUInteger)threshold 

YYDiskCache類持有一個YYKVStorage對象侄非,但是你不能手動的去控制YYKVStorage對象的YYKVStorageType蕉汪。YYDiskCache類初始化提供一個threshold的參數(shù)流译,默認(rèn)的為20KB。然后根據(jù)這個值得大小來確定YYKVStorageType的類型肤无。

YYKVStorageType type;
    if (threshold == 0) {
        type = YYKVStorageTypeFile;
    } else if (threshold == NSUIntegerMax) {
        type = YYKVStorageTypeSQLite;
    } else {
        type = YYKVStorageTypeMixed;
    }
    YYKVStorage *kv = [[YYKVStorage alloc] initWithPath:path type:type];

因?yàn)?code>YYDiskCache類的操作其實(shí)就是去操作持有的YYKVStorage對象先蒋,所以下面的部分會比較建簡略骇钦。

寫入和更新

在調(diào)用YYKVStorage對象的儲存操作前主要做了下面幾項(xiàng)操作:
1.key和object的判空容錯機(jī)制
2.利用runtime機(jī)制去取extendedData數(shù)據(jù)
3.根據(jù)是否定義了_customArchiveBlock來判斷選擇序列化object還是block回調(diào)得到value
4.value的判空容錯機(jī)制
5.根據(jù)YYKVStorage的type判斷以及_inlineThreshold和value值得長度來判斷是否選擇以文件的形式儲存value值宛渐。上面我們說過當(dāng)value比較大的時候文件儲存速度比較快速。
6.如果_customFileNameBlock為空眯搭,則根據(jù)key通過md5加密得到轉(zhuǎn)化后的filename.不然直接拿到_customFileNameBlock關(guān)聯(lián)的filename窥翩。生成以后操作文件的路徑
做完上面的操作則直接調(diào)用YYKVStorage儲存方法,下面是實(shí)現(xiàn)代碼:

- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
    if (!key) return;
    if (!object) {
        [self removeObjectForKey:key];
        return;
    }
    //runtime 取extended_data_key的value
    NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object];
    NSData *value = nil;
    if (_customArchiveBlock) {
        //block返回
        value = _customArchiveBlock(object);
    } else {
        @try {
            //序列化
            value = [NSKeyedArchiver archivedDataWithRootObject:object];
        }
        @catch (NSException *exception) {
            // nothing to do...
        }
    }
    if (!value) return;
    NSString *filename = nil;
    if (_kv.type != YYKVStorageTypeSQLite) {
        //長度判斷這個儲存方式鳞仙,value.length當(dāng)大于_inlineThreshold則文件儲存
        if (value.length > _inlineThreshold) {
            //將key 進(jìn)行md5加密
            filename = [self _filenameForKey:key];
        }
    }
    
    Lock();
    [_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData];
    Unlock();
}

讀取

讀取操作一般都是和寫入操作相輔相成的寇蚊,我們來看看在調(diào)用YYKVStorage對象的讀取操作后做了哪些操作:
1.item.value的判空容錯機(jī)制
2.根據(jù)_customUnarchiveBlock值來判斷是直接將item.value block回調(diào)還是反序列化成object
3.根據(jù)object && item.extendedData 來決定是否runtime添加extended_data_key屬性

- (id<NSCoding>)objectForKey:(NSString *)key {
    if (!key) return nil;
    Lock();
    YYKVStorageItem *item = [_kv getItemForKey:key];
    Unlock();
    if (!item.value) return nil;
    
    id object = nil;
    if (_customUnarchiveBlock) {
        object = _customUnarchiveBlock(item.value);
    } else {
        @try {
            object = [NSKeyedUnarchiver unarchiveObjectWithData:item.value];
        }
        @catch (NSException *exception) {
            // nothing to do...
        }
    }
    if (object && item.extendedData) {
        [YYDiskCache setExtendedData:item.extendedData toObject:object];
    }
    return object;
}

刪除

刪除操作就是直接調(diào)用的YYKVStorage對象來操作了。

- (void)removeObjectForKey:(NSString *)key {
    if (!key) return;
    Lock();
    [_kv removeItemForKey:key];
    Unlock();
}

當(dāng)然棍好,YYDiskCacheYYMemoryCache一樣也給你提供了一些類似limit的接口供你操作仗岸。

- (void)trimToCount:(NSUInteger)count;
- (void)trimToCost:(NSUInteger)cost;
- (void)trimToAge:(NSTimeInterval)age;

YYKVStorage不一樣的是,作為更高層的YYDiskCache是一個線程安全的類借笙。你應(yīng)該使用YYDiskCache而不是YYKVStorage扒怖。

最后再帶一筆食物端最頂端的男人YYCache,當(dāng)他寫入的時候會同時調(diào)用YYDiskCache磁盤操作和YYMemoryCache內(nèi)存操作业稼。讀取的時候先從內(nèi)存讀取盗痒,因?yàn)樵趦?nèi)存的讀取速度比磁盤快很多,如果沒有讀取到數(shù)據(jù)才會去磁盤讀取低散。

讀后感只有四個字:

如沐春風(fēng)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末俯邓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子熔号,更是在濱河造成了極大的恐慌稽鞭,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件引镊,死亡現(xiàn)場離奇詭異川慌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)祠乃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門梦重,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人亮瓷,你說我怎么就攤上這事琴拧。” “怎么了嘱支?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵蚓胸,是天一觀的道長挣饥。 經(jīng)常有香客問我,道長沛膳,這世上最難降的妖魔是什么扔枫? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮锹安,結(jié)果婚禮上短荐,老公的妹妹穿的比我還像新娘。我一直安慰自己叹哭,他們只是感情好忍宋,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著风罩,像睡著了一般糠排。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上超升,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天入宦,我揣著相機(jī)與錄音,去河邊找鬼室琢。 笑死乾闰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的研乒。 我是一名探鬼主播汹忠,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼雹熬!你這毒婦竟也來了宽菜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤竿报,失蹤者是張志新(化名)和其女友劉穎铅乡,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烈菌,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阵幸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了芽世。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挚赊。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖济瓢,靈堂內(nèi)的尸體忽然破棺而出荠割,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布蔑鹦,位于F島的核電站夺克,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嚎朽。R本人自食惡果不足惜铺纽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望哟忍。 院中可真熱鬧狡门,春花似錦、人聲如沸魁索。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽粗蔚。三九已至,卻和暖如春饶火,著一層夾襖步出監(jiān)牢的瞬間鹏控,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工肤寝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留当辐,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓鲤看,卻偏偏與公主長得像缘揪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子义桂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

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

  • 概述 上一篇主要講解了YYMemoryCache的文件結(jié)構(gòu)找筝,分析了YYMemoryCache類的相關(guān)方法,本章主要...
    egoCogito_panf閱讀 3,770評論 3 11
  • 概述 上一篇主要講解了YYCache的文件結(jié)構(gòu)慷吊,分析了YYCache類的相關(guān)方法袖裕,本章主要分析內(nèi)存緩存類YYMem...
    egoCogito_panf閱讀 3,142評論 2 12
  • 前言 本篇文章將帶來YYCache的解讀,YYCache支持內(nèi)存和本地兩種方式的數(shù)據(jù)存儲溉瓶。我們先拋出兩個問題: Y...
    老馬的春天閱讀 3,597評論 17 32
  • 概述 YYCache是一個用來封裝客戶端緩存功能的庫急鳄,實(shí)現(xiàn)了二級緩存的機(jī)制,即同時具備內(nèi)存緩存和硬盤緩存的功能堰酿。 ...
    egoCogito_panf閱讀 2,363評論 0 5
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法疾宏,類相關(guān)的語法,內(nèi)部類的語法触创,繼承相關(guān)的語法坎藐,異常的語法,線程的語...
    子非魚_t_閱讀 31,581評論 18 399