iOS標準庫中經常使用數據結構和算法之cache

上一篇:iOS標準庫中經常使用數據結構和算法以內存池git

📝緩存Cache

緩存是以鍵值對的形式進行數據的存儲和檢索,內部採用哈希表實現。當系統出現內存壓力時則會釋放掉部分緩存的鍵值對。 iOS系統提供了一套基於OC語言的高級緩存庫NSCache,同時也提供一套基於C語言實現的緩存庫libcache.dylib,其中NSCache是基於libcache.dylib實現的高級類庫,而且這兩個庫都是線程安全的。 本文主要介紹基於C語言的緩存庫的各類API函數。github

頭文件: #include <cache.h>, #include <cache_callbacks.h> 平臺: iOS系統算法

1、緩存對象的建立和關閉

功能:建立或者銷燬一個緩存對象。 函數簽名緩存

int cache_create(const char *name, cache_attributes_t *attrs, cache_t **cache_out);
int cache_destroy(cache_t *cache);
複製代碼

參數: name:[in] 建立緩存時用來指定緩存的字符串名稱,不能爲空。 attrs: [in] 設置緩存的屬性。不能爲空。 cache_out: [out] 返回建立的緩存對象。 return: [out] 成功操做返回0,不然返回非0 描述: 緩存對象是一個容器對象,其緩存的內容是一個個鍵值對,至於這些鍵值對是什麼類型的數據,如何控制鍵值對的數據的生命週期,如何判斷兩個鍵是不是相同的鍵等等這些信息緩存對象自己是沒法得知,所以須要咱們明確的告訴緩存對象如何去操做這些鍵值信息。這也就是爲何在建立緩存對象時須要指定屬性這個參數了。屬性的參數類型是一個cache_attributes_t結構體。這個結構體的大部分數據成員都是函數指針,這些函數指針就是用來實現對鍵值進行操做的各類策略。安全

struct cache_attributes_s {
    uint32_t version;  //緩存對象的版本信息
    cache_key_hash_cb_t key_hash_cb;  //對鍵執行hash計算的函數,不能爲空                         
    cache_key_is_equal_cb_t key_is_equal_cb;  //判斷兩個鍵是否相等的函數,不能爲空                      
    
    cache_key_retain_cb_t  key_retain_cb;   //鍵加入緩存時調用,用於增長鍵的引用計數或者進行內存拷貝。
    cache_release_cb_t key_release_cb;  //鍵的釋放處理函數,用於對鍵的內存管理使用。
    cache_release_cb_t value_release_cb;  //值的釋放處理函數,用於對值的內存管理使用。                         
    
    cache_value_make_nonpurgeable_cb_t value_make_nonpurgeable_cb;   //當值的引用計數從0變爲1時對值內存進行非purgeable的處理函數。
    cache_value_make_purgeable_cb_t value_make_purgeable_cb;  //當值的引用計數變爲0時對值進行purgeable的處理函數。這個函數的做用是爲了解決當內存吃緊時自動釋放所分配的內存。    
    
    void *user_data;  //附加數據,這個附加數據會在全部的這些回調函數中出現。

	// Added in CACHE_ATTRIBUTES_VERSION_2
	cache_value_retain_cb_t value_retain_cb;   //值增長引用計數的函數,用於對值的內存管理使用。
};
typedef struct cache_attributes_s cache_attributes_t;

複製代碼

上述的各類回調函數的格式都在cache.h中有明確的定義,所以這裏就再也不展開介紹了。通常狀況下咱們一般都會將字符串或者整數來做爲鍵使用,所以當你採用字符串或者整數做爲鍵時,系統預置了一系列緩存對象屬性的默認實現函數。這些函數的聲明在cache_callbacks.h文件中bash

/*
 * Pre-defined callback functions.
 */

//用於鍵進行哈希計算的預置函數
CACHE_PUBLIC_API uintptr_t cache_key_hash_cb_cstring(void *key, void *unused);
CACHE_PUBLIC_API uintptr_t cache_key_hash_cb_integer(void *key, void *unused);
//用於鍵進行相等比較的預置函數
CACHE_PUBLIC_API bool cache_key_is_equal_cb_cstring(void *key1, void *key2, void *unused);
CACHE_PUBLIC_API bool cache_key_is_equal_cb_integer(void *key1, void *key2, void *unused);

//鍵值進行釋放的函數,這函數默認實現就是調用free函數,所以若是採用這個函數進行釋放處理則鍵值須要從堆中進行內存分配。
CACHE_PUBLIC_API void cache_release_cb_free(void *key_or_value, void *unused);

 //對值進行purgeable處理的預置函數。
CACHE_PUBLIC_API void cache_value_make_purgeable_cb(void *value, void *unused);
CACHE_PUBLIC_API bool cache_value_make_nonpurgeable_cb(void *value, void *unused);

複製代碼

示例代碼數據結構

//下面代碼用於建立一個以字符串爲鍵的緩存對象,其中的緩存對象的屬性中的各個成員函數採用的是系統默認預約的函數。
 #include <cache.h>
 #include <cache_callbcaks.h>

 cache_t *im_cache;
 cache_attributes_t attrs = {
         .version = CACHE_ATTRIBUTES_VERSION_2,
         .key_hash_cb = cache_key_hash_cb_cstring,
         .key_is_equal_cb = cache_key_is_equal_cb_cstring,
         .key_retain_cb = my_copy_string,
         .key_release_cb = cache_release_cb_free,
         .value_release_cb = cache_release_cb_free,
  };
  cache_create("com.acme.im_cache", &attrs, &im_cache);
複製代碼

2、緩存對象中鍵值對的設置和獲取以及刪除

功能:用於處理鍵值對在緩存中的添加、獲取和刪除操做。 函數簽名:數據結構和算法

//將鍵值對添加到緩存,或者替換掉原有的鍵值對。
 int cache_set_and_retain(cache_t *cache, void *key, void *value, size_t cost);

//從緩存中根據鍵獲取值
int cache_get_and_retain(cache_t *cache, void *key, void **value_out);

//將緩存中的值引用計數減1,當引用計數爲0時則清理值分配的內存或者銷燬值分配的內存。
int cache_release_value(cache_t *cache, void *value);

//從緩存中刪除鍵值。
int cache_remove(cache_t *cache, void *key);

複製代碼

參數: cache:[in] 緩存對象。 key:[in] 添加或者獲取或者刪除時的鍵。 cost:[in] 添加緩存時的成本代價,值越大鍵值在緩存中保留的時間就越長久。 value:[in] 添加時的值。 value_out: [out] 用於值獲取時的輸出。函數

描述post

  1. cache_set_and_retain 函數用於將鍵值對放入緩存中,並指定cost值。當將一個鍵添加到緩存時,系統內部分別會調用緩存屬性cache_attributes_t結構體中的key_retain_cb來實現對鍵的內存的管理,若是這個函數設置爲NULL的話那就代表咱們須要本身負責鍵的生命週期的管理。由於緩存對象內部是經過哈希表來進行數據的存儲和檢索的,因此在將鍵值對加入緩存時,還須要提供對鍵進哈希計算和比較的屬性函數key_hash_cb,key_is_equal_cb。 而對於值來講,當值加入緩存時系統會將值的引用計數設置爲1,若是咱們想自行處理值在緩存中的內存保存則須要指定緩存屬性中的value_retain_cb來實現。加入緩存中的值是能夠爲NULL的。最後的cost參數用於指定這個鍵值對的成本值,值越小在緩存中保留的時間就越少,反之亦然。

  2. cache_get_and_retain函數用來根據鍵獲取對應的值,若是緩存中沒有保存對應的鍵值對,或者鍵值對被丟棄,或者值所分配的內存被清除則value_out返回NULL,而且函數返回特殊的值ENOENT。每調用一次值的獲取,緩存對象都會增長值的引用計數。所以當咱們再也不須要訪問返回的值時則須要調用手動調用cache_release_value函數來減小緩存對象中值的引用計數。而當值的引用計數變爲0時則值分配的內存會設置爲可清理(purgeable)或者被銷燬掉。

  3. cache_remove函數用於刪除緩存中的鍵值對。當刪除緩存中的鍵值對時,緩存對象會調用屬性結構體中的key_release_cb函數進行鍵的內存銷燬,以及若是值的引用計數變爲0時則會調用value_release_cb函數進行值的內存銷燬。

示例代碼:

#include <cache.h>
#include <cache_callbacks.h>

void main()
{
    cache_attributes_t attr;
    attr.key_hash_cb = cache_key_hash_cb_cstring;
    attr.key_is_equal_cb = cache_key_is_equal_cb_cstring;
    attr.key_retain_cb =  NULL;
    attr.key_release_cb = cache_release_cb_free;
    attr.version = CACHE_ATTRIBUTES_VERSION_2;
    attr.user_data = NULL;
    attr.value_retain_cb = NULL;
    attr.value_release_cb = cache_release_cb_free;
    attr.value_make_purgeable_cb = cache_value_make_purgeable_cb;
    attr.value_make_nonpurgeable_cb = cache_value_make_nonpurgeable_cb;
    
    //建立緩存
    cache_t *cache = NULL;
    int ret = cache_create("com.test", &attr, &cache);
    
    //將鍵值對放入緩存
    char *key = malloc(4);
    strcpy(key, "key");
    char *val = malloc(4);
    strcpy(val, "val");
    ret = cache_set_and_retain(cache, key, val, 0);
    ret = cache_release_value(cache, val);
    //獲取鍵值對,使用後要釋放。
    char *val2 = NULL;
    ret = cache_get_and_retain(cache, key, (void**)&val2);
    ret = cache_release_value(cache, val2);
    
    //刪除鍵值
    cache_remove(cache, key);
    
    //銷燬緩存。
    cache_destroy(cache);
}
複製代碼

3、緩存中鍵值對的清理策略和值的訪問策略

緩存的做用是會對保存在裏面的鍵值對進行丟棄,這取決於當前內存的使用狀況以及其餘一些場景。在調用cache_set_and_retain將鍵值對添加到緩存時還會指定一個cost值,值越大被丟棄的可能性就越低。在上面的介紹中有說明緩存會對鍵值對中的值進行引用計數管理。當調用cache_set_and_retain時值引用計數將設置爲1,調用cache_get_and_retain函數獲取值時若是鍵值對在緩存中則會增長值的引用計數。而不須要訪問和操做值時咱們須要調用cache_release_value函數來將引用計數減1。當值的引用計數變爲0時就會當即或者之後發生以下的事情:

  1. 若是咱們在緩存的屬性結構體中設置了value_make_purgeable_cb函數則會調用這個函數代表此時值是能夠被清理的。被清理的意思是說爲值分配的物理內存隨時有可能會被回收。所以當值被設置爲可被清理狀態時就不能繼續去直接訪問值所分配的內存了。
  2. 若是在此以前鍵值對由於函數cache_remove的調用而被從緩存中刪除,則會調用屬性結構體中的value_release_cb函數來執行值內存的銷燬處理。
  3. 若是由於系統內存的壓力致使須要丟棄緩存中的鍵值對時,就會把值引用計數爲0的鍵值對丟棄掉!注意:只有值引用計數爲0時纔會緩存被丟棄。

每次對緩存中的值的訪問都須要經過cache_get_and_retain函數來執行,當調用cache_get_and_retain函數時會發生以下事情:

  1. 判斷當前鍵值對是否在緩存中,若是再也不則值返回NULL。
  2. 若是鍵值對在緩存中。而且值的引用計數爲0,就會判斷緩存的結構體屬性中是否存在value_make_nonpurgeable_cb函數,若是存在則會調用value_make_nonpurgeable_cb函數將值的內存設置爲不可清理,若是設置爲不可清理返回爲false則代表此時值的內存已經被清理了,這時候鍵值對將會從緩存中丟棄,而且cache_get_and_retain函數將返回值爲NULL。固然若是value_make_nonpurgeable_cb函數爲空則不會發生這一步。
  3. 增長值的引用計數,並返回值。

歡迎你們訪問歐陽大哥2013的github地址簡書地址

相關文章
相關標籤/搜索