YYCache 源码学习(二):YYDiskCache
整體思路
從作者的《YYCache 設計思路》一文中可以看出,作者在設計YYDiskCache之前做了充分的測試:iPhone 6 64G 下,SQLite 寫入性能比直接寫文件要高,但讀取性能取決于數(shù)據(jù)大小:當單條數(shù)據(jù)小于 20K 時,數(shù)據(jù)越小 SQLite 讀取性能越高;單條數(shù)據(jù)大于 20K 時,直接寫為文件速度會更快一些。
YYDiskCache的磁盤緩存結(jié)合使用了文件儲存和數(shù)據(jù)庫儲存。
個人理解:在進行磁盤緩存的時候,會判斷要儲存數(shù)據(jù)的大小,如果數(shù)據(jù)小于20K,則直接存入數(shù)據(jù)庫(數(shù)據(jù)儲存到inline_data字段,此時filename為空)。如果數(shù)據(jù)大于20K,先把數(shù)據(jù)以文件形式進行存儲,然后再在數(shù)據(jù)庫中儲存對應的文件名(此時inline_data為NULL,filename為文件地址),具體的可以結(jié)合下文中提到的磁盤緩存的文件結(jié)構(gòu)來看。
磁盤緩存的核心類是YYKVStorage,他主要封裝了文件儲存操作和SQLite數(shù)據(jù)庫的操作。YYDiskCache是對YYKVStorage的封裝,拋出的API和內(nèi)存緩存相似,都有數(shù)據(jù)讀寫和修剪內(nèi)存。
磁盤緩存的文件結(jié)構(gòu)
/*File:/path//manifest.sqlite/manifest.sqlite-shm/manifest.sqlite-wal/data//e10adc3949ba59abbe56e057f20f883e/e10adc3949ba59abbe56e057f20f883e/trash//unused_file_or_folderSQL:create table if not exists manifest (key text,filename text,size integer,inline_data blob,modification_time integer,last_access_time integer,extended_data blob,primary key(key)); create index if not exists last_access_time_idx on manifest(last_access_time);*/這個結(jié)構(gòu)我們不需要多說什么,只提一個小點,作者在path路徑下面設計了一個/data/和一個/trash/。刪除文件是一個比較耗時的操作,在刪除文件的時候,先進行文件的移動,然后在一個子線程中處理要刪掉的文件,提高了整體的效率。
實現(xiàn) LRU
磁盤緩存對緩存淘汰算法的實現(xiàn)就比較簡單了,因為每次存儲都有對應的數(shù)據(jù)庫記錄,而且表中設計了last_access_time這個字段,我們可以直接使用數(shù)據(jù)庫的排序語句就可以找到最不常用的文件了。
代碼分析
1.
- (sqlite3_stmt *)_dbPrepareStmt:(NSString *)sql {if (![self _dbCheck] || sql.length == 0 || !_dbStmtCache) return NULL;sqlite3_stmt *stmt = (sqlite3_stmt *)CFDictionaryGetValue(_dbStmtCache, (__bridge const void *)(sql));if (!stmt) {int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);if (result != SQLITE_OK) {if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));return NULL;}CFDictionarySetValue(_dbStmtCache, (__bridge const void *)(sql), stmt);} else {sqlite3_reset(stmt);}return stmt; }這個方法是提前生成了sql語句的句柄,可以理解成提前把sql語句編譯成字節(jié)碼留給后面的執(zhí)行函數(shù)(當前不執(zhí)行)。同時,作者使用
_dbStmtCache對語句進行緩存,下次使用時可以更快度的加載出來。
2.
- (BOOL)_dbClose {if (!_db) return YES;int result = 0;BOOL retry = NO;BOOL stmtFinalized = NO;if (_dbStmtCache) CFRelease(_dbStmtCache);_dbStmtCache = NULL;do {retry = NO;result = sqlite3_close(_db);// 狀態(tài)為busy或者lockif (result == SQLITE_BUSY || result == SQLITE_LOCKED) {if (!stmtFinalized) {stmtFinalized = YES;sqlite3_stmt *stmt;//sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt);//表示從數(shù)據(jù)庫pDb中對應的pStmt語句開始一個個往下找出相應prepared語句,如果pStmt為nil,那么就從pDb的第一個prepared語句開始。while ((stmt = sqlite3_next_stmt(_db, nil)) != 0) {//釋放數(shù)據(jù)庫中的prepared語句資源sqlite3_finalize(stmt);retry = YES;}}} else if (result != SQLITE_OK) {if (_errorLogsEnabled) {NSLog(@"%s line:%d sqlite close failed (%d).", __FUNCTION__, __LINE__, result);}}} while (retry);_db = NULL;return YES; }這個是關(guān)閉數(shù)據(jù)庫的方法,_dbStmtCache中緩存了我們使用的句柄,所以首先要釋放掉了_dbStmtCache。
在真正關(guān)閉數(shù)據(jù)庫的代碼中使用了do-while循環(huán),因為一次訪問數(shù)據(jù)庫并不一定成功,數(shù)據(jù)庫可能是busy或者lock的狀態(tài),所以要使用一個循環(huán)來多次訪問。
如果為能關(guān)閉數(shù)據(jù)庫,作者使用了sqlite3_next_stmt一個個的找出prepared語句,并使用sqlite3_finalize釋放了prepared資源(防止內(nèi)存泄露)。
其他的就沒什么好說的了,主要就是一些sql語句的用法,這些大家看一下,碰到陌生的api谷歌一下就有了 ~ 具體的文件的操作,比較常用,看起來就容易很多。
總結(jié)
以上是生活随笔為你收集整理的YYCache 源码学习(二):YYDiskCache的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: React单页如何规划路由、设计Stor
- 下一篇: 基于 DataLakeAnalytics