/* 索引记录常量 */
#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结构体 */
}