【6】自己写数据库函数库 — 存储记录

存储记录的函数接口为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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值