iOS标准库中常用数据结构和算法之KV数据库

1,621 阅读6分钟

上一篇: 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而有些系统则没有这个限制。