【3】字节写数据库函数库 — 打开数据库

/* 索引记录常量 */
#define IDXLEN_SZ    4		/* 索引记录长度所占字节数 */
#define SEP 		':'		/* 分隔符 */
#define SPACE     	' '		/* 空格符 */
#define NEWLINE		'\n'	/* 换行符 */

/* 散列表相关常量 */
#define PTR_SZ 		6		/* 指针所占字符数 */
#define PTR_MAX 	999999	/* 文件最大偏移量(六位数最大值) */
#define NHASH_DEF	137		/* 散列表大小 */
#define FREE_OFF	0		/* 空链表在索引文件中的偏移量 */
#define HASH_OFF	PTR_SZ	/* 散列表在索引文件中的偏移量 */

typedef unsigned long DBHASH;
typedef unsigned long COUNT;

/* 记录一个打开数据库的所有信息 */
typedef struct {
	int   idxfd;	/* 索引文件描述符 */
	int   datfd;	/* 数据文件描述符 */
	char *idxbuf;	/* 实际索引记录,包括键值、数据记录偏移量、数据记录长度 */
	char *datbuf;
	char *name;
	off_t idxoff;	/* 当前索引记录的偏移量 */
	size_t idxlen;	/* 当前索引记录长度 */

	off_t datoff;	/* 数据文件中该记录的偏移量 */
	size_t datlen;	/* 该数据记录长度 */

	off_t ptrval;	/* 下一条索引记录的偏移量 */
	off_t ptroff;	/* 前一条索引记录的偏移量 */
	off_t chainoff;	/* 一条散列链的偏移量 */
	off_t hashoff;	/* 散列表的起始位置 */
	DBHASH nhash;	/* 散列表大小,这里为137 */

	/* 操作成功或失败的记录 */
	COUNT cnt_delok;
	COUNT cnt_delerr;
	COUNT cnt_fetchok;
	COUNT cnt_fetcherr;	/* 获取数据失败记录 */
	COUNT cnt_nextrec;
	COUNT cnt_stor1;	/* 插入操作,追加新纪录 */
	COUNT cnt_stor2;	/* 插入操作,找到空记录 */
	COUNT cnt_stor3;
	COUNT cnt_stor4;
	COUNT cnt_storerr;
} DB;

/* 内部函数 */
static DB     *_db_alloc(int);
static void    _db_dodelete(DB *);
static int     _db_find_and_lock(DB *, const char *, int);
static int     _db_findfree(DB *, int, int);
static void    _db_free(DB *);
static DBHASH  _db_hash(DB *, const char *);
static char   *_db_readdat(DB *);
static off_t   _db_readidx(DB *, off_t);
static off_t   _db_readptr(DB *, off_t);
static void    _db_writedat(DB *, const char *, off_t, int);
static void    _db_writeidx(DB *, const char *, off_t, int, off_t);
static void    _db_writeptr(DB *, off_t, off_t);

/* 打开或建立一个数据库
 * 调用完此函数后,索引文件pathname.idx只有一个空闲链表和一个散列表
 * 数据文件pathname.dat为空
 */
DBHANDLE db_open(const char *pathname, int oflag, ...)
{
	DB *db;
	int len, mode;
	size_t i;
	char asciiptr[PTR_SZ + 1], hash[(NHASH_DEF+1) * PTR_SZ + 2];	/* +2表示空字符和换行符 */
	struct stat statbuff;

	len = strlen(pathname);
	if ((db = _db_alloc(len)) == NULL)	/* 给db结构体分配空间 */
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}

	db->nhash   = NHASH_DEF;	/* 散列表大小 = 137 */
	db->hashoff = HASH_OFF;		/* 跳过第一个空闲链表,散列表起始位置 = 6 */

	if (oflag & O_CREAT)	/* 创建文件 */
	{
		/* 因为参数是在栈内连续存放,所以可以根据可变参数之前的那个参数地址,
		 * 推算出所有可变参数的地址,从而获得每个可变参数
		 */
		va_list ap;				/* 实质是一个指针 */
		va_start(ap, oflag);	/* 初始化ap,使它指向可变参数列表中第一个参数 */
		mode = va_arg(ap, int);	/* 获取可变参数,mode保存有访问权限,知道类型就能够获取值 */
		va_end(ap);				/* 关闭指针,使它为NULL */

		/* 创建文件 */
		strcpy(db->name, pathname);
		strcat(db->name, ".idx");
		db->idxfd = open(db->name, oflag, mode);	/* 创建索引文件pathname.idx */
		strcpy(db->name + len, ".dat");
		db->datfd = open(db->name, oflag, mode);	/* 创建数据文件pathname.dat */
	}
	else	/* 打开现有数据库 */
	{
		strcpy(db->name, pathname);
		strcat(db->name, ".idx");		
		db->idxfd = open(db->name, oflag);
		strcpy(db->name + len, ".dat");
		db->datfd = open(db->name, oflag);
	}

	/* 这里一次性检查上面所有的open是否成功,节省了代码空间 */
	if (db->idxfd < 0 || db->datfd < 0)
	{
		/* 打开失败 */
		printf("%d\n", __LINE__);
		_db_free(db);	/* 清理DB结构 */
		return NULL;	/* 打开失败,返回NULL */
	}

	/* 注意,要两个条件同时满足 */
	if ((oflag & (O_CREAT | O_TRUNC)) == (O_CREAT | O_TRUNC))
	{
		/* 初始化新创建的数据库 */
		/* 长度为0表示锁住整个文件 */
		if (writew_lock(db->idxfd, 0, SEEK_SET, 0) < 0)
		{
			printf("%d\n", __LINE__);
			exit(-1);
		}

		if (fstat(db->idxfd, &statbuff) < 0)	/* 这个函数一定要上锁 */
		{
			printf("%d\n", __LINE__);
			exit(-1);
		}

		if (statbuff.st_size == 0)
		{
			/* 初始化索引文件,*表示占位符,占用PTR_SZ个字符 */
			/* 先构造散列表,然后写入索引文件 */
			sprintf(asciiptr, "%*d", PTR_SZ, 0);	/* asciiptr = "_ _ _ _ _ 0" */
			hash[0] = 0;							/* 为了让strcat函数覆盖此空字符 */
			for (i = 0; i < NHASH_DEF + 1; i++)
				strcat(hash, asciiptr);				/* strcat会覆盖hash尾部的'\0' */
			strcat(hash, "\n");						/* hash和asciiptr尾部都没有空字符 */
			i = strlen(hash);						/* i = 829 = 138 * 6 + 1 */

			if (write(db->idxfd, hash, i) != i)		/* 哈希表写入索引文件 */
			{
				printf("%d\n", __LINE__);
				exit(-1);
			}
		}
		if (un_lock(db->idxfd, 0, SEEK_SET, 0) < 0)	/* 文件解锁 */
		{
			printf("%d\n", __LINE__);
			exit(-1);
		}
	}
	db_rewind(db);	/* 跳过散列表,使db->idxoff指向第一条索引记录 */
	return db;
}


db_open就是用来打开数据库的,它的函数原型和open系统调用很像,实际上就是根据传入db_open的参数来进行open操作的。当数据库存在,则直接打开,否则创建数据库,包括创建索引文件my_db.idx和数据文件my_db.dat两个文件。整个db_open函数围绕核心结构体DB进行初始化。DB就代表了一个数据库,内部保存有数据库当前的各种数据。用户调用的所有数据库接口都要传入一个响应的DB结构体。而db_open就是返回一个DB结构体,供后续函数接口使用。

以下是分配DB结构体所需的空间:

/* 分配、初始化DB结构体 */
static DB *_db_alloc(int namelen)
{
	DB *db;

	if ((db = calloc(1, sizeof(DB))) == NULL)
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}

	/* calloc将全部成员变成了0,这里重新设置为-1表示此时文件描述符无效 */
	db->idxfd = db->datfd = -1;						/* _db_free 为了检测是否需要关闭 */

	if ((db->name = malloc(namelen + 5)) == NULL)	/* +5保存".idx"或".dat"和空字符 */
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}

	if ((db->idxbuf = malloc(IDXLEN_MAX + 2)) == NULL)	/* +2保存空字符和换行符 */
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}

	if ((db->datbuf = malloc(DATLEN_MAX + 2)) == NULL)	/* +2保存空字符和换行符 */
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}

	return db;
}

db->name负责保存文件名,db->idxbuf负责保存索引记录的内容,db->datbuf负责存储实际的数据内容。

和open操作相反,close操作则执行相反的操作:free掉所有分配出来的空间:

void db_close(DBHANDLE h)
{
	_db_free((DB *)h);
}

/* 释放资源和DB结构 */
static void _db_free(DB *db)
{
	/* _db_alloc 函数把描述符都设成了-1
	 * 只要打开了就一定非负
	 */
	if (db->idxfd >= 0)
		close(db->idxfd);
	if (db->datfd >= 0)
		close(db->datfd);

	/* 销毁缓冲区 */
	if (db->idxbuf != NULL)
		free(db->idxbuf);
	if (db->datbuf != NULL)
		free(db->datbuf);
	if (db->name != NULL)
		free(db->name);

	free(db);	/* 销毁整个DB结构体 */
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值