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