YYCache 源碼學習(二):YYDiskCache

總體思路

從做者的《YYCache 設計思路》一文中能夠看出,做者在設計YYDiskCache以前作了充分的測試:iPhone 6 64G 下,SQLite 寫入性能比直接寫文件要高,但讀取性能取決於數據大小:當單條數據小於 20K 時,數據越小 SQLite 讀取性能越高;單條數據大於 20K 時,直接寫爲文件速度會更快一些。算法

YYDiskCache的磁盤緩存結合使用了文件儲存和數據庫儲存。sql

我的理解:在進行磁盤緩存的時候,會判斷要儲存數據的大小,若是數據小於20K,則直接存入數據庫(數據儲存到inline_data字段,此時filename爲空)。若是數據大於20K,先把數據以文件形式進行存儲,而後再在數據庫中儲存對應的文件名(此時inline_data爲NULL,filename爲文件地址),具體的能夠結合下文中提到的磁盤緩存的文件結構來看。數據庫

磁盤緩存的核心類是YYKVStorage,他主要封裝了文件儲存操做和SQLite數據庫的操做。YYDiskCache是對YYKVStorage的封裝,拋出的API和內存緩存類似,都有數據讀寫和修剪內存。api

磁盤緩存的文件結構
/*
 File:
 /path/
      /manifest.sqlite
      /manifest.sqlite-shm
      /manifest.sqlite-wal
      /data/
           /e10adc3949ba59abbe56e057f20f883e
           /e10adc3949ba59abbe56e057f20f883e
      /trash/
            /unused_file_or_folder
 
 SQL:
 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或者lock
        if (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谷歌一下就有了 ~ 具體的文件的操做,比較經常使用,看起來就容易不少。

相關文章
相關標籤/搜索