存储记录的函数接口为db_store,代码如下:
/* 将一条记录写到数据库中
* 返回值:
* 0 —— 成功
* 1 —— 记录已存在
* -1 —— 错误
*/
int db_store(DBHANDLE h, const char *key, const char *data, int flag)
{
DB *db = h;
int rc, keylen, datlen;
off_t ptrval;
/* 先验证flag的值 */
if (flag != DB_INSERT && flag != DB_REPLACE && flag != DB_STORE)
{
errno = EINVAL;
return -1;
}
keylen = strlen(key);
datlen = strlen(data) + 1; /* +1保存换行符 */
if (datlen < DATLEN_MIN || datlen > DATLEN_MAX) /* 数据长度必须在2~1024字节之间 */
{
printf("%d\n", __LINE__);
exit(-1);
}
/* 先查看记录是否已存在 */
if (_db_find_and_lock(db, key, 1) < 0)
{
/* 记录不存在 */
if (flag == DB_REPLACE)
{
rc = -1; /* 出错 */
db->cnt_storerr++;
errno = ENOENT;
goto doreturn;
}
/* 指向一条散列链的开头,chainoff已经在_db_find_and_lock函数中设置妥当 */
ptrval = _db_readptr(db, db->chainoff);
/* 在空闲链表中搜索一条已删除的记录
* 它的键长度和数据长度跟传入的参数相等
*/
if (_db_findfree(db, keylen, datlen) < 0)
{
/* 情况1 */
/* 没有找到这样的空闲记录,则要添加新的记录到这条散列链末尾
* 注意这里的顺序,是先写数据文件,设置好datoff和datlen之后再写索引文件
*/
_db_writedat(db, data, 0, SEEK_END);
/* 新记录的下一条记录为ptrval,相当于把新记录插入到散列链开头 */
_db_writeidx(db, key, 0, SEEK_END, ptrval);
/* idxoff已经在_db_writeidx函数中被设置妥当
* 使散列链开头指向新插入的节点
*/
_db_writeptr(db, db->chainoff, db->idxoff);
db->cnt_stor1++;
/* 注意,上述操作确实是在文件末尾添加新索引记录和数据记录
* 但逻辑上,是插入到散列链的开头,这是修改各个指针域的数值来完成的
*/
}
else
{
/* 情况2 */
/* 找到了对应大小的空记录,那么只需修改空记录即可
* _db_findfree函数会从空闲链表移除这条即将被使用的空记录
*/
_db_writedat(db, data, db->datoff, SEEK_SET);
_db_writeidx(db, key, db->idxoff, SEEK_SET, ptrval);
_db_writeptr(db, db->chainoff, db->idxoff);
db->cnt_stor2++;
}
}
else
{
/* 情况3 */
/* 记录已存在 */
if (flag == DB_INSERT)
{
rc = 1; /* 1表示记录已存在 */
db->cnt_storerr++;
goto doreturn;
}
/* 以下执行替换操作 */
if (datlen != db->datlen)
{
/* 新记录长度和已存在记录长度不一样
* 那么会先删除现有记录,然后和情况1一样重新添加一个新的记录
*/
_db_dodelete(db); /* 先删除老记录,放入空闲链表开头 */
ptrval = _db_readptr(db, db->chainoff);
_db_writedat(db, data, 0, SEEK_END);
_db_writeidx(db, key, 0, SEEK_END, ptrval);
_db_writeptr(db, db->chainoff, db->idxoff);
db->cnt_stor3++;
}
else
{
/* 情况4 */
/* 长度相同,直接执行替换操作 */
_db_writedat(db, data, db->datoff, SEEK_SET);
db->cnt_stor4++;
}
}
rc = 0; /* 0表示成功 */
doreturn:
/* _db_find_and_lock加了锁,这里要解锁 */
if (un_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0)
{
printf("%d\n", __LINE__);
exit(-1);
}
return rc;
}
此函数在存储时分为了四种情况:
- 需要存储的记录不存在,那么在空闲链表中查找大小匹配的空记录,找不到则在文件末尾添加新记录。
- 需要存储的记录不存在,那么在空闲链表中查找大小匹配的空记录,找到则在则重用空闲记录。
- 需要存储的记录存在,如果新旧记录的长度不相同,则删除久记录,插入新记录。
- 需要存储的记录存在,且新旧记录长度相同,则直接覆盖原来的内容即可。
辅助函数_db_findfree用来在空闲链表中查找大小相匹配的空闲记录以重新利用,代码如下:
/* 在空闲链表中查找键长度和数据记录长度跟传入参数相匹配的记录
* 注意,这里只需要比较key长度和datlen,因为另外一些字段长度固定
* 返回值:
* 0 —— 查找成功
* -1 —— 查找失败
*/
static int _db_findfree(DB *db, int keylen, int datlen)
{
int rc;
off_t offset, nextoffset, saveoffset;
/* 这里要对空闲链表加锁,因为可能要从空闲链表中重用记录 */
if (writew_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0)
{
printf("%d\n", __LINE__);
exit(-1);
}
saveoffset = FREE_OFF;
offset = _db_readptr(db, saveoffset); /* 第一条空闲记录的偏移地址 */
/* 循环遍历空闲链表 */
while (offset != 0)
{
/* 读取索引记录
* db->idxbuf = "key \0 datoff \0 datlen \0"
* strlen函数遇到空字符则停止,所以能够正确的计算key的长度
*/
nextoffset = _db_readidx(db, offset);
if (strlen(db->idxbuf) == keylen && db->datlen == datlen)
break; /* 找到了 */
saveoffset = offset;
offset = nextoffset;
}
if (offset == 0)
rc = -1; /* 没有找到 */
else
{
/* 从空闲链表中删除找到的记录,使上一条记录指向下一条记录
* saveoffset保存了上一条记录的偏移量
* db->ptrval保存了上一条记录的偏移量
*/
_db_writeptr(db, saveoffset, db->ptrval);
rc = 0;
}
/* 解锁空闲链表 */
if (un_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0)
{
printf("%d\n", __LINE__);
exit(-1);
}
return rc;
}