YYCache學(xué)習(xí)緩存設(shè)計

前言

日常的iOS開發(fā)過程中,經(jīng)常會用到緩存,但是什么樣的緩存才能被叫做優(yōu)秀的緩存属划,或者說優(yōu)秀的緩存應(yīng)該具備哪些特質(zhì)?YYCache我認為是一個比較優(yōu)秀的緩存候生,代碼邏輯清晰同眯,注釋詳盡,加上自身不算太大的代碼量使得其閱讀非常簡單唯鸭,更可貴的是它的性能還很高须蜗。

YYCache簡介

我們先來簡單看一下 YYCache 的代碼結(jié)構(gòu),YYCache 是由 YYMemoryCache 與 YYDiskCache 兩部分組成的,其中 YYMemoryCache 作為高速內(nèi)存緩存明肮,而 YYDiskCache 則作為低速磁盤緩存菱农。

通常一個緩存是由內(nèi)存緩存和磁盤緩存組成,內(nèi)存緩存提供容量小但高速的存取功能晤愧,磁盤緩存提供大容量但低速的持久化存儲大莫。

@interface YYCache : NSObject

/** 緩存名稱 */

@property (copy, readonly) NSString *name;

/** memoryCache*/

@property (strong, readonly) YYMemoryCache *memoryCache;

/** diskCache*/

@property (strong, readonly) YYDiskCache *diskCache;

/**判斷key是否存在*/

- (BOOL)containsObjectForKey:(NSString *)key;

/**判斷key是否存在,并執(zhí)行block*/

- (void)containsObjectForKey:(NSString *)key withBlock:(nullable?void(^)(NSString *key,?BOOL?contains))block;

/**獲取key值對應(yīng)的對象 會阻塞調(diào)用的進程*/

- (nullable id)objectForKey:(NSString *)key;

/** 獲取key值對應(yīng)的對象官份,并執(zhí)行block*/

- (void)objectForKey:(NSString *)key withBlock:(nullable?void(^)(NSString *key, id object))block;

/** 對某個key設(shè)置對象只厘,阻塞線程*/

- (void)setObject:(nullable id)object forKey:(NSString *)key;

/** 設(shè)置key的對象,線程會立即返回舅巷,設(shè)置成功后回調(diào)block*/

- (void)setObject:(nullable id)object forKey:(NSString *)key withBlock:(nullable?void(^)(void))block;

/**刪除key對應(yīng)的對象 阻塞線程 */

- (void)removeObjectForKey:(NSString *)key;

/**刪除key對應(yīng)的object 線程會立即返回羔味,刪除成功后回調(diào)block*/

- (void)removeObjectForKey:(NSString *)key withBlock:(nullable?void(^)(NSString *key))block;

/**清空緩存*/

- (void)removeAllObjects;

/** 清空緩存, 線程會立即返回钠右,清空成功后回調(diào)block */

- (void)removeAllObjectsWithBlock:(void(^)(void))block;

/**清空緩存赋元, 線程會立即返回,后臺線程執(zhí)行block*/

- (void)removeAllObjectsWithProgressBlock:(nullable?void(^)(int?removedCount,?int?totalCount))progress ?endBlock:(nullable?void(^)(BOOL?error))end;

上邊整理了幾個常用的方法飒房,做了簡單的中文注釋搁凸,從代碼中我們可以看到 YYCache 中持有 YYMemoryCache 與 YYDiskCache,并且對外提供了一些接口狠毯。這些接口基本都是基于 Key 和 Value 設(shè)計的护糖,類似于 iOS 原生的字典類接口(增刪改查)

YYMemoryCache

YYMemoryCache 是一個高速的內(nèi)存緩存,用于存儲鍵值對嚼松。它與 NSDictionary 相反嫡良,Key 被保留并且不復(fù)制。API 和性能類似于 NSCache献酗,所有方法都是線程安全的寝受。

YYMemoryCache 使用 LRU(least-recently-used) 算法來驅(qū)逐對象。介紹一下LRU:

LRU(Least?recently?used罕偎,最近最少使用)算法根據(jù)數(shù)據(jù)的歷史訪問記錄來進行淘汰數(shù)據(jù)很澄,其核心思想是“如果數(shù)據(jù)最近被訪問過,那么將來被訪問的幾率也更高”颜及。最常見的實現(xiàn)是使用一個鏈表保存緩存數(shù)據(jù)痴怨,詳細算法實現(xiàn)如下:


? ? ? ? ? ?1. 新數(shù)據(jù)插入到鏈表頭部;


? ? ? ? ? ?2.?每當緩存命中(即緩存數(shù)據(jù)被訪問)器予,則將數(shù)據(jù)移到鏈表頭部;


? ? ? ? ? ?3.?當鏈表滿的時候捐迫,將鏈表尾部的數(shù)據(jù)丟棄乾翔。

? ? ?分析

? ? ? ?【命中率】

? ? ? ? ? ? 當存在熱點數(shù)據(jù)時,LRU的效率很好,但偶發(fā)性的反浓、周期性的批量操作會導(dǎo)致LRU命中率急劇下降萌丈,緩存污染情況比較嚴重。

? ? ? ?【復(fù)雜度】

? ? ? ? ? ? 實現(xiàn)簡單雷则。

? ? ? 【代價】?

? ? ? ? ? ? 命中時需要遍歷鏈表辆雾,找到命中的數(shù)據(jù)塊索引,然后需要將數(shù)據(jù)移到頭部月劈。

YYMemoryCache是線程安全的

@implementation YYMemoryCache {

????pthread_mutex_t _lock; // 線程鎖度迂,旨在保證 YYMemoryCache 線程安全

????_YYLinkedMap *_lru; // _YYLinkedMap,YYMemoryCache 通過它間接操作緩存對象

????dispatch_queue_t _queue; // 串行隊列猜揪,用于 YYMemoryCache 的 trim 操作

}

? 沒錯惭墓,YYMemoryCache使用?pthread_mutex線程鎖來確保線程安全。最初YYMemoryCache 這里使用的鎖是?OSSpinLock?自旋鎖而姐,后面有人在 Github 向作者提?issue?反饋?OSSpinLock?不安全腊凶,經(jīng)過作者的確認(詳見?不再安全的 OSSpinLock)最后選擇用?pthread_mutex?替代?OSSpinLock。

具體來說拴念,如果一個低優(yōu)先級的線程獲得鎖并訪問共享資源钧萍,這時一個高優(yōu)先級的線程也嘗試獲得這個鎖,它會處于 spin lock 的忙等狀態(tài)從而占用大量 CPU政鼠。此時低優(yōu)先級線程無法與高優(yōu)先級線程爭奪 CPU 時間风瘦,從而導(dǎo)致任務(wù)遲遲完不成、無法釋放 lock缔俄。這并不只是理論上的問題弛秋,libobjc 已經(jīng)遇到了很多次這個問題了,于是蘋果的工程師停用了 OSSpinLock俐载。

_YYLinkedMap 與 _LinkedMapNode

YYMemoryCache 無法直接操作緩存蟹略,而是通過內(nèi)部的?_YYLinkedMapNode?與?_YYLinkedMap?來的操作緩存對象。這兩個類對于上文中提到的 LRU 緩存算法的理解至關(guān)重要遏佣。


@interface _YYLinkedMapNode : NSObject {

????@package

????__unsafe_unretained _YYLinkedMapNode *_prev; // __unsafe_unretained 是為了性能優(yōu)化挖炬,節(jié)點被 _YYLinkedMap 的 _dic 強引用

????__unsafe_unretained _YYLinkedMapNode *_next; // __unsafe_unretained 是為了性能優(yōu)化,節(jié)點被 _YYLinkedMap 的 _dic 強引用

????id _key;

????id _value;

????NSUInteger _cost;? // 記錄開銷状婶,對應(yīng) YYMemoryCache 提供的 cost 控制

????NSTimeInterval _time;// 記錄時間意敛,對應(yīng) YYMemoryCache 提供的 age 控制

}


@end

@interface _YYLinkedMap : NSObject {

????@package

????CFMutableDictionaryRef _dic; // // 不要直接設(shè)置該對象

????NSUInteger _totalCost;

????NSUInteger _totalCount;

????_YYLinkedMapNode *_head; // MRU, 最常用節(jié)點,不要直接修改它

????_YYLinkedMapNode *_tail; // LRU, 最常用節(jié)點膛虫,不要直接修改它

????BOOL _releaseOnMainThread; // 對應(yīng) YYMemoryCache 的 releaseOnMainThread

????BOOL _releaseAsynchronously; // 對應(yīng) YYMemoryCache 的 releaseAsynchronously

- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;

- (void)bringNodeToHead:(_YYLinkedMapNode *)node;

- (void)removeNode:(_YYLinkedMapNode *)node;

- (_YYLinkedMapNode *)removeTailNode;

- (void)removeAll;

}


對數(shù)據(jù)結(jié)構(gòu)與算法不陌生的同學(xué)草姻,應(yīng)該一眼就看的出來?_YYLinkedMapNode?與?_YYLinkedMap?這的本質(zhì)。其實就是雙向鏈表節(jié)點和雙向鏈表稍刀。

? _YYLinkedMapNode?作為雙向鏈表節(jié)點撩独,除了基本的?_prev敞曹、_next,還有鍵值緩存基本的?_key?與?_value综膀,我們可以把?_YYLinkedMapNode?理解為 YYMemoryCache 中的一個緩存對象澳迫。_YYLinkedMap?作為由?_YYLinkedMapNode?節(jié)點組成的雙向鏈表,使用?CFMutableDictionaryRef _dic?字典存儲?_YYLinkedMapNode剧劝。這樣在確保?_YYLinkedMapNode?被強引用的同時橄登,能夠利用字典的 Hash 快速定位用戶要訪問的緩存對象,這樣既符合了鍵值緩存的概念又省去了自己實現(xiàn)的麻煩讥此÷G拢總得來說 YYMemoryCache 是通過使用?_YYLinkedMap雙向鏈表來操作?_YYLinkedMapNode?緩存對象節(jié)點的。


YYDiskCache簡介


YYDiskCache 是一個線程安全的磁盤緩存暂论,用于存儲由 SQLite 和文件系統(tǒng)支持的鍵值對(類似于 NSURLCache 的磁盤緩存)面褐。

YYDiskCache 具有以下功能:

通過 LRU 算法來刪除對象。

它可以被配置為當沒有可用的磁盤空間時自動驅(qū)逐緩存對象取胎。

它可以自動抉擇每個緩存對象的存儲類型(sqlite/file)以便提供更好的性能表現(xiàn)展哭。

@interface YYDiskCache : NSObject

#pragma mark - Attribute

@property (nullable, copy) NSString *name; // 緩存名稱,默認為 nil

@property (readonly) NSString *path; // 緩存路徑


@property (readonly) NSUInteger inlineThreshold; // 閾值闻蛀,大于閾值則存儲類型為 file匪傍;否則存儲類型為 sqlite

@property (nullable, copy) NSData *(^customArchiveBlock)(id object); // 用來替換 NSKeyedArchiver,你可以使用該代碼塊以支持沒有 conform `NSCoding` 協(xié)議的對象

@property (nullable, copy) id (^customUnarchiveBlock)(NSData *data); // 用來替換 NSKeyedUnarchiver觉痛,你可以使用該代碼塊以支持沒有 conform `NSCoding` 協(xié)議的對象

@property (nullable, copy) NSString *(^customFileNameBlock)(NSString *key); // 當一個對象將以 file 的形式保存時役衡,該代碼塊用來生成指定文件名。如果為 nil薪棒,則默認使用 md5(key) 作為文件名

#pragma mark - Limit

@property NSUInteger countLimit; // 緩存對象數(shù)量限制手蝎,默認無限制,超過限制則會在后臺逐出一些對象以滿足限制

@property NSUInteger costLimit; // 緩存開銷數(shù)量限制俐芯,默認無限制棵介,超過限制則會在后臺逐出一些對象以滿足限制

@property NSTimeInterval ageLimit; // 緩存時間限制,默認無限制吧史,超過限制則會在后臺逐出一些對象以滿足限制

@property NSUInteger freeDiskSpaceLimit; // 緩存應(yīng)該保留的最小可用磁盤空間(以字節(jié)為單位)邮辽,默認無限制,超過限制則會在后臺逐出一些對象以滿足限制


@property NSTimeInterval autoTrimInterval; // 緩存自動清理時間間隔贸营,默認 60s

@property BOOL errorLogsEnabled; // 是否開啟錯誤日志

#pragma mark - Initializer

- (nullable instancetype)initWithPath:(NSString *)path

??????????????????????inlineThreshold:(NSUInteger)threshold NS_DESIGNATED_INITIALIZER;

- (BOOL)containsObjectForKey:(NSString *)key;

- (nullable id)objectForKey:(NSString *)key;

- (void)setObject:(nullable id)object forKey:(NSString *)key;

- (void)removeObjectForKey:(NSString *)key;

- (void)removeAllObjects;

- (NSInteger)totalCount;

- (NSInteger)totalCost;

#pragma mark - Trim

- (void)trimToCount:(NSUInteger)count;

- (void)trimToCost:(NSUInteger)cost;

- (void)trimToAge:(NSTimeInterval)age;

#pragma mark - Extended Data

+ (nullable NSData *)getExtendedDataFromObject:(id)object;

+ (void)setExtendedData:(nullable NSData *)extendedData toObject:(id)object;

@end


YYDiskCache 是基于 sqlite 和 file 來做的磁盤緩存吨述,我們的緩存對象可以自由的選擇存儲類型,下面簡單對比一下:

sqlite: 對于小數(shù)據(jù)(例如 NSNumber)的存取效率明顯高于 file钞脂。

file: 對于較大數(shù)據(jù)(例如高質(zhì)量圖片)的存取效率優(yōu)于 sqlite揣云。

所以 YYDiskCache 使用兩者配合,靈活的存儲以提高性能冰啃。

YYDiskCache 內(nèi)部是基于一個單例 NSMapTable 管理灵再,

NSMapTable 是類似于字典的集合肋层,但具有更廣泛的可用內(nèi)存語義。NSMapTable 是 iOS6 之后引入的類翎迁,它基于 NSDictionary 建模,但是具有以下差異:

鍵/值可以選擇 “weakly” 持有净薛,以便于在回收其中一個對象時刪除對應(yīng)條目汪榔。

它可以包含任意指針(其內(nèi)容不被約束為對象)。

您可以將 NSMapTable 實例配置為對任意指針進行操作肃拜,而不僅僅是對象

每當一個 YYDiskCache 被初始化時痴腌,其實會先到 NSMapTable 中獲取對應(yīng) path 的 YYDiskCache 實例,如果獲取不到才會去真正的初始化一個 YYDiskCache 實例燃领,并且將其引用在 NSMapTable 中士聪,這樣做也會提升不少性能。

- (instancetype)initWithPath:(NSString *)path

?????????????inlineThreshold:(NSUInteger)threshold {

????//初始化判斷忽略

????// 先從 NSMapTable 單例中根據(jù) path 獲取 YYDiskCache 實例猛蔽,如果獲取到就直接返回該實例

????YYDiskCache *globalCache = _YYDiskCacheGetGlobal(path);

????if (globalCache) return globalCache;

????// 沒有獲取到則初始化一個 YYDiskCache 實例

????// 要想初始化一個 YYDiskCache 首先要初始化一個 YYKVStorage

????YYKVStorage *kv = [[YYKVStorage alloc] initWithPath:path type:type];

????if (!kv) return nil;

????// 根據(jù)剛才得到的 kv 和 path 入?yún)⒊跏蓟粋€ YYDiskCache 實例剥悟,代碼太長省略

????...

? ? // 開啟遞歸清理,會根據(jù) _autoTrimInterval 對 YYDiskCache trim

????[self _trimRecursively];

????// 向 NSMapTable 單例注冊新生成的 YYDiskCache 實例

????_YYDiskCacheSetGlobal(self);

????// App 生命周期通知相關(guān)代碼曼库,省略

????...

????return self;

}

dispatch_semaphore 是信號量区岗,但當信號總量設(shè)為 1 時也可以當作鎖來。在沒有等待情況出現(xiàn)時毁枯,它的性能比 pthread_mutex 還要高慈缔,但一旦有等待情況出現(xiàn)時,性能就會下降許多种玛。相對于 OSSpinLock 來說藐鹤,它的優(yōu)勢在于等待時不會消耗 CPU 資源。對磁盤緩存來說赂韵,它比較合適娱节。

YYKVStorageItem 與 YYKVStorage

在上邊的代碼中,我們看到了YYKVStorage右锨,YYDiskCache是通過YYKVStorage來操作緩存對象(sqlite/file)括堤,YYKVStorage 和 YYMemoryCache 中的雙向鏈表?_YYLinkedMap扮演的角色是一樣的,而對應(yīng)于?_YYLinkedMap?中的節(jié)點?_YYLinkedMapNode绍移,YYKVStorage 中也有一個類 YYKVStorageItem 充當著與緩存對象的角色悄窃。


/**

?用于YYStorage存儲鍵值對和屬性信息

?通常情況下,我們不應(yīng)該直接使用這個類蹂窖。

?*/

@interface YYKVStorageItem : NSObject

@property (nonatomic, strong) NSString *key;??????????????? ///< key?

@property (nonatomic, strong) NSData *value;??????????????? ///< value?

@property (nullable, nonatomic, strong) NSString *filename; ///< filename (nil if inline)

@property (nonatomic) int size;???????????????????????????? ///< value's size in bytes?

@property (nonatomic) int modTime;????????????????????????? ///< modification unix timestamp

@property (nonatomic) int accessTime;?????????????????????? ///< last access unix timestamp

@property (nullable, nonatomic, strong) NSData *extendedData; ///< extended data (nil if no extended data)

@end

/**

?YYKVStorage 是基于 sqlite 和file的鍵值存儲轧抗。

?通常情況下,我們不應(yīng)該直接使用這個類瞬测。

?@warning?

??這個類的實例是 *非* 線程安全的横媚,你需要確保

??只有一個線程可以同時訪問該實例纠炮。如果你真的

??需要在多線程中處理大量的數(shù)據(jù),應(yīng)該分割數(shù)據(jù)

??到多個 KVStorage 實例(分片)灯蝴。

?*/

@interface YYKVStorage : NSObject

#pragma mark - Attribute

@property (nonatomic, readonly) NSString *path;??????? /// storage 路徑

@property (nonatomic, readonly) YYKVStorageType type;? /// storage 類型

@property (nonatomic) BOOL errorLogsEnabled;?????????? /// 是否開啟錯誤日志

#pragma mark - Initializer

- (nullable instancetype)initWithPath:(NSString *)path type:(YYKVStorageType)type NS_DESIGNATED_INITIALIZER;

#pragma mark - Save Items

- (BOOL)saveItem:(YYKVStorageItem *)item;

...

#pragma mark - Remove Items

- (BOOL)removeItemForKey:(NSString *)key;

...

#pragma mark - Get Items

- (nullable YYKVStorageItem *)getItemForKey:(NSString *)key;

...

#pragma mark - Get Storage Status

- (BOOL)itemExistsForKey:(NSString *)key;

- (int)getItemsCount;

- (int)getItemsSize;

@end

這里我們看一下YYKVStorageType恢口,這個枚舉決定著 YYKVStorage 的存儲類型

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,

};

再看YYKVStorage代碼的同時,發(fā)現(xiàn)一個細節(jié)

? ??CFMutableDictionaryRef _dbStmtCache;

是 YYKVStorage 中的私有成員穷躁,它是一個可變字典充當著 sqlite3_stmt 緩存的角色耕肩。

- (sqlite3_stmt *)_dbPrepareStmt:(NSString *)sql {

????if (![self _dbCheck] || sql.length == 0 || !_dbStmtCache) return NULL;

????// 先嘗試從 _dbStmtCache 根據(jù)入?yún)?sql 取出已緩存 sqlite3_stmt

????sqlite3_stmt *stmt = (sqlite3_stmt *)CFDictionaryGetValue(_dbStmtCache, (__bridge const void *)(sql));

????if (!stmt) {

????????// 如果沒有緩存再從新生成一個 sqlite3_stmt

????????int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);

????????// 生成結(jié)果異常則根據(jù)錯誤日志開啟標識打印日志

????????if (result != SQLITE_OK) {

????????????if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));

????????????return NULL;

????????}

????????// 生成成功則放入 _dbStmtCache 緩存

????????CFDictionarySetValue(_dbStmtCache, (__bridge const void *)(sql), stmt);

????} else {

????????sqlite3_reset(stmt);

????}

????return stmt;

}

這樣就可以省去一些重復(fù)生成 sqlite3_stmt 的開銷。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末问潭,一起剝皮案震驚了整個濱河市猿诸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌狡忙,老刑警劉巖梳虽,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異灾茁,居然都是意外死亡窜觉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門删顶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竖螃,“玉大人,你說我怎么就攤上這事逗余√嘏兀” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵录粱,是天一觀的道長腻格。 經(jīng)常有香客問我,道長啥繁,這世上最難降的妖魔是什么菜职? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮旗闽,結(jié)果婚禮上酬核,老公的妹妹穿的比我還像新娘。我一直安慰自己适室,他們只是感情好嫡意,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著捣辆,像睡著了一般蔬螟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上汽畴,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天旧巾,我揣著相機與錄音耸序,去河邊找鬼。 笑死鲁猩,一個胖子當著我的面吹牛坎怪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播廓握,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼芋忿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了疾棵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤痹仙,失蹤者是張志新(化名)和其女友劉穎是尔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體开仰,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡拟枚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了众弓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恩溅。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖谓娃,靈堂內(nèi)的尸體忽然破棺而出脚乡,到底是詐尸還是另有隱情,我是刑警寧澤滨达,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布奶稠,位于F島的核電站,受9級特大地震影響捡遍,放射性物質(zhì)發(fā)生泄漏锌订。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一画株、第九天 我趴在偏房一處隱蔽的房頂上張望辆飘。 院中可真熱鬧,春花似錦谓传、人聲如沸蜈项。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽战得。三九已至,卻和暖如春庸推,著一層夾襖步出監(jiān)牢的瞬間常侦,已是汗流浹背浇冰。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留聋亡,地道東北人肘习。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像坡倔,于是被迫代替她去往敵國和親漂佩。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

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