存储记录的函数接口为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; }
/* 在空闲链表中查找键长度和数据记录长度跟传入参数相匹配的记录 * 注意,这里只需要比较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; }