iOS標準庫中常用資料結構和演算法之KV資料庫

歐陽大哥2013發表於2019-04-22

上一篇: iOS標準庫中常用資料結構和演算法之雜湊表

?KV資料庫

對於結構化資料的儲存一般我們使用關係型資料庫,而對於基於key-value型別的資料儲存則不適合用關係型資料庫。因此iOS系統也內建了一套基於key-value儲存的檔案資料庫:ndbm。

功能: 一套基於key-value形式儲存的資料庫。

標頭檔案: #include <ndbm.h>

平臺: BSD Unix

一、建立、開啟、關閉

功能: 資料庫檔案的建立、開啟、關閉。

函式簽名

//資料庫檔案的建立或者開啟
DBM * dbm_open(const char *file, int open_flags, mode_t file_mode);
//資料庫檔案的關閉
void  dbm_close(DBM *db);

複製程式碼

引數:

file:[in] 指定資料庫的檔名,系統在開啟和建立時會在內部自動新增.db的字尾名稱,因此我們不需要指定字尾副檔名部分。

open_flags: [in] 檔案的開啟屬性,一般傳遞O_RDWR | O_CREAT 表明讀寫以及不存在時建立。

file_mode:[in] 檔案的訪問許可權模式,一般設定為0660。

db:[in] 用於執行資料庫關閉的控制程式碼,這個控制程式碼是由資料庫檔案開啟所返回的。

return:[out] 資料庫建立成功時返回的資料庫控制程式碼指標,資料庫控制程式碼指標是一個DBM型別的資料,這個型別對我們透明,也不需要我們去關心, 當開啟失敗時返回NULL。

二、新增

功能:將某個key-value鍵值對新增到資料庫中。系統並沒有對key-value的內容做限制,但是在進行新增處理時必須要轉化為如下定義的結構體:

typedef struct {
	void *dptr;    //記憶體資料的地址
	size_t dsize;  //記憶體資料的尺寸。
} datum;
複製程式碼

函式簽名

int dbm_store(DBM *db, datum key, datum content, int store_mode);
複製程式碼

引數

db: [in] 資料庫控制程式碼。

key:[in] 要插入的key部分的內容。

content:[in] 要插入的value部分的內容。

store_mode:[in] 插入的模式,可以指定為DBM_INSERT或DBM_REPLACE。當指定為DBM_INSERT時表明是插入,如果此時資料庫中存在對應的key時則此次操作會返回失敗;當指定為DBM_REPLACE時則當不存在時會執行新增處理,而當對應的key存在時就會執行替換處理。

return:[out] 當新增成功時返回0,當返回1時表明插入一個已經存在的key,當返回-1時表明插入失敗。

三、刪除

功能: 從資料庫中刪除某個key-value鍵值對。

函式簽名:


 int dbm_delete(DBM *db, datum key);

複製程式碼

引數:

db: [in] 資料庫控制程式碼。

key:[in] 要刪除的鍵。

return:[out] 如果返回0則刪除成功,返回1則表明不存在指定的key,返回-1則是其他錯誤。

四、查詢

功能: 根據指定的key從資料庫中查詢對應的value值。

函式簽名:

 datum dbm_fetch(DBM *db, datum key);

複製程式碼

引數

db:[in] 資料庫控制程式碼。

key:[in] 查詢指定的key

return:[out] 系統返回一個結構體datum, 儲存返回的value值。如果key沒有對應的value 值, 那麼返回的datum中的dptr的值將是NULL。我們不需要對返回的值進行記憶體釋放,也不能對返回的值的內容進行修改。

五、磁碟同步

功能: 將記憶體中儲存的key-value值同步到磁碟中去

標頭檔案: #include<db.h>

平臺: BSD Unix

描述:

針對ndbm資料庫,系統並沒有直接提供將記憶體資料同步到磁碟的API。除了提供ndbm外,系統提供了一個更加通用的檔案資料庫介面,這些介面定義在db.h檔案中。從標頭檔案的宣告中我們可以看到系統提供的db的型別以及關於資料庫控制程式碼的詳細定義:

 //三種資料庫型別:B樹、HASH表、記錄
typedef enum { DB_BTREE, DB_HASH, DB_RECNO } DBTYPE;

//通用的資料庫控制程式碼定義。
typedef struct __db {
	DBTYPE type;			/* 型別 */
	int (*close)(struct __db *);  //關閉庫函式
	int (*del)(const struct __db *, const DBT *, unsigned int);  //刪除元素函式
	int (*get)(const struct __db *, const DBT *, DBT *, unsigned int);  //獲取元素函式
	int (*put)(const struct __db *, DBT *, const DBT *, unsigned int); //設定元素函式
	int (*seq)(const struct __db *, DBT *, DBT *, unsigned int); //序列號生成函式
	int (*sync)(const struct __db *, unsigned int);  //同步函式
	void *internal;			/* 內部結構 */
	int (*fd)(const struct __db *);   //得到檔案描述符函式
} DB;
複製程式碼

而我們使用的key-value庫內部其實是一種DB_HASH型別的資料庫檔案。同時DBM型別其實也是一種DB型別。因此當我們需要進行磁碟同步時只需要將DBM型別的控制程式碼轉化為DB型別的控制程式碼,然後呼叫其中sync函式就可以實現磁碟檔案的同步了。這樣我們就可以在需要將記憶體資料儲存到磁碟是直接呼叫同步函式而不需要通過關閉檔案來達到磁碟儲存的目的。

示例程式碼:

  DBM *dbm = XXX;  //假設DBM是在其他地方開啟的資料庫檔案控制程式碼.
  DB *db = (DB*)dbm;  //轉化為DB型別指標。
  db->sync(db, 0);  //這裡呼叫這個函式就可以實現記憶體資料到磁碟的同步處理了。

複製程式碼

六、遍歷

功能:系統提供了兩個遍歷的函式,分別是獲取整個資料庫中最開始的key值,以及獲取下一個有效的key值的函式.

函式簽名:

//獲取第一個儲存的key值。
 datum dbm_firstkey(DBM *db);

//獲取下一個儲存的key值。
 datum dbm_nextkey(DBM *db);
複製程式碼

引數

db:[in]資料庫控制程式碼

return:[out] 返回對應的key值,如果沒有key值那麼返回的結構體中的dptr的值將是NULL。

描述

你可以通過這兩個函式來依次遍歷整個資料庫中的key值,然後再結合dbm_fetch來獲取對應的value。需要注意的是如果某次遍歷期間中執行了插入或者刪除操作則應該要重新進行遍歷,否則得到的結果未可知。

示例程式碼:

//遍歷函式
void traversendbm(DBM *dbm)
{
    printf("start:-------------\n");

    datum key = dbm_firstkey(dbm);
    while (key.dptr != NULL)
    {
        datum val = dbm_fetch(dbm, key);
        if (val.dptr != NULL)
        {
            printf("key=%s, val=%s\n",key.dptr, val.dptr);
        }
        
        key = dbm_nextkey(dbm);
    }
    
    printf("end:-------------\n");
}

void main()
{
    DBM *dbm = dbm_open("/Users/apple/testdb", O_RDWR | O_CREAT, 0660);
    
    datum key1,val1,key2,val2;
    key1.dptr = "aa";
    key1.dsize = 3;
    key2.dptr = "bb";
    key2.dsize = 3;
    val1.dptr = "vvv1";
    val1.dsize = 5;
    val2.dptr = "vvv2";
    val2.dsize = 5;
    
    //新增
    if (dbm_store(dbm, key1, val1, DBM_INSERT) < 0)
    {
        printf ("insert error:%d\n", dbm_error(dbm));
    }
    
    if (dbm_store(dbm, key2, val2, DBM_INSERT) < 0)
    {
        printf ("insert error:%d\n", dbm_error(dbm));
    }
    
    traversendbm(dbm);
    
    //替換
    val1.dptr = "vvv3";
    val1.dsize = 5;
    if (dbm_store(dbm, key1, val1, DBM_REPLACE) < 0)
    {
        printf ("insert error:%d\n", dbm_error(dbm));
    }
    
    traversendbm(dbm);
    
    //刪除
    int ret1 = dbm_delete(dbm, key1);  
   
    trandbm(dbm);
    
     //關閉 
    dbm_close(dbm);
}

複製程式碼

在iOS系統的內部實現中所有的新增或者刪除操作如果不執行dbm_close的話那麼都不會實際儲存到磁碟檔案中。因此如果你在iOS系統中使用這套API則要記得在合適的時候執行資料庫關閉處理。當然你也可以通過上述提供的磁碟同步的方法來實現記憶體到磁碟的儲存處理。

有一些Unix系統中對key-value的長度限制為1024而有些系統則沒有這個限制。

相關文章