YYCache 源码学习(二):YYDiskCache
整體思路
從作者的《YYCache 設計思路》一文中可以看出,作者在設計YYDiskCache之前做了充分的測試:iPhone 6 64G 下,SQLite 寫入性能比直接寫文件要高,但讀取性能取決于數據大小:當單條數據小于 20K 時,數據越小 SQLite 讀取性能越高;單條數據大于 20K 時,直接寫為文件速度會更快一些。
YYDiskCache的磁盤緩存結合使用了文件儲存和數據庫儲存。
個人理解:在進行磁盤緩存的時候,會判斷要儲存數據的大小,如果數據小于20K,則直接存入數據庫(數據儲存到inline_data字段,此時filename為空)。如果數據大于20K,先把數據以文件形式進行存儲,然后再在數據庫中儲存對應的文件名(此時inline_data為NULL,filename為文件地址),具體的可以結合下文中提到的磁盤緩存的文件結構來看。
磁盤緩存的核心類是YYKVStorage,他主要封裝了文件儲存操作和SQLite數據庫的操作。YYDiskCache是對YYKVStorage的封裝,拋出的API和內存緩存相似,都有數據讀寫和修剪內存。
磁盤緩存的文件結構
/*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);*/這個結構我們不需要多說什么,只提一個小點,作者在path路徑下面設計了一個/data/和一個/trash/。刪除文件是一個比較耗時的操作,在刪除文件的時候,先進行文件的移動,然后在一個子線程中處理要刪掉的文件,提高了整體的效率。
實現 LRU
磁盤緩存對緩存淘汰算法的實現就比較簡單了,因為每次存儲都有對應的數據庫記錄,而且表中設計了last_access_time這個字段,我們可以直接使用數據庫的排序語句就可以找到最不常用的文件了。
代碼分析
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語句編譯成字節碼留給后面的執行函數(當前不執行)。同時,作者使用
_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);// 狀態為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);//表示從數據庫pDb中對應的pStmt語句開始一個個往下找出相應prepared語句,如果pStmt為nil,那么就從pDb的第一個prepared語句開始。while ((stmt = sqlite3_next_stmt(_db, nil)) != 0) {//釋放數據庫中的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; }這個是關閉數據庫的方法,_dbStmtCache中緩存了我們使用的句柄,所以首先要釋放掉了_dbStmtCache。
在真正關閉數據庫的代碼中使用了do-while循環,因為一次訪問數據庫并不一定成功,數據庫可能是busy或者lock的狀態,所以要使用一個循環來多次訪問。
如果為能關閉數據庫,作者使用了sqlite3_next_stmt一個個的找出prepared語句,并使用sqlite3_finalize釋放了prepared資源(防止內存泄露)。
其他的就沒什么好說的了,主要就是一些sql語句的用法,這些大家看一下,碰到陌生的api谷歌一下就有了 ~ 具體的文件的操作,比較常用,看起來就容易很多。
總結
以上是生活随笔為你收集整理的YYCache 源码学习(二):YYDiskCache的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: React单页如何规划路由、设计Stor
- 下一篇: 基于 DataLakeAnalytics