讲解:https://blog.csdn.net/cinmyheart/article/details/42094953
大多数访问数据库的函数库使用两个文件存储信息:索引文件和数据文件。索引文件包含索引值(键)和指向数据文件中对应数据记录的指针。为提高按键查询的速度和效率,可用散列法和B+树组织索引文件,本文采用固定大小的散列表,
并采用链表法解决散列冲突。
由于只有一个索引文件,所以每条数据记录只能有一个键(不支持第二个键)。
索引文件由三部分组成:空闲链表指针、散列表和索引记录。索引指针(ptr)字段中实际存储的是以ASCII码数字形式记录的文件中的偏移量。
当给定一个键要做数据库中寻找一条记录是,db_fetch根据该键值即使散列值,由此散列值可确定散列表中的一条散列链(链表指针字段可以为0,表示一条空的散列链)。沿着这条散列链,可以找到所有具有该散列值的索引记录。当遇到一条索引记录的链表指针字段为0是,表示到达了此散列链的末尾。
t4.c
#include "apue.h"
#include "apue_db.h"
#include <fcntl.h>
int main(void)
{
DBHANDLE db;
if((db = db_open("db4", O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) == NULL)
err_sys("db_open error");
if(db_store(db, "Alpah", "data1", DB_INSERT) != 0)
err_quit("db_store error for alpha");
if(db_store(db, "beta", "Data for beta", DB_INSERT) != 0)
err_quit("db_store error for beta");
if(db_store(db, "gamma", "record3", DB_INSERT) != 0)
err_quit("db_store error for gamma");
db_close(db);
exit(0);
}
结果:
结果解释:
散列链表的大小为137,其中有三条散列链表写了数据。
共三条记录,每条记录都是所在链表的头节点。
第一条记录的偏移量为829, 即“ 0 10Alpah:0:6”的偏移量为829
0 10Alpha:0:6
第一个0:位于本链表中的下一条记录的偏移,0表示空
10:记录长度,即”Alpha:0:6”的长度为10
0:数据在数据文件,即db4.dat中的偏移
6:数据的长度,即”data1”的长度
apue_db.h
#ifndef _APUE_DB_H /* 保证只包含该头文件一次 */
#define _APUE_DB_H
typedef void * DBHANDLE; /* 对数据库的一个有效引用,隔离应用程序和数据库的实现细节 */
DBHANDLE db_open(const char *pathname, int oflag, ...);
void db_close(DBHANDLE h);
char *db_fetch(DBHANDLE h, const char *key);
int db_store(DBHANDLE h, const char *key, const char *data, int flag);
int db_delete(DBHANDLE h, const char *key);
void db_rewind(DBHANDLE h);
char *db_nextrec(DBHANDLE h, char *key);
/* 可以传送给db_store函数的合法标志 */
#define DB_INSERT 1 /* insert new record only */
#define DB_REPLACE 2 /* replace existing record */
#define DB_STORE 3 /* replace or insert */
/* 实现的基本限制,为支持更大的数据库可更改这些限制 */
#define IDXLEN_MIN 6 /* key, sep, start, sep, length, \n --- 最小索引长度
1字节键、1字节分隔符、1字节起始偏移量、
另一个字节分隔符、1字节长度和1字节换行符。*/
#define IDXLEN_MAX 1024 /* arbitrary */
#define DATLEN_MIN 2 /* data byte, newline */
#define DATLEN_MAX 1024 /* arbitrary */
#endif /* _APUE_DB_H */
db.c
#include "apue.h"
#include "apue_db.h"
#include <fcntl.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/uio.h>
#define IDXLEN_SZ 4 /* 索引记录长度 */
#define SEP ':' /* 用某些字符(例如冒号、换行符)作为数据库中的分隔符 */
#define SPACE ' ' /* 当删除一条记录时,在其中全部填入空格符 */
#define NEWLINE '\n'
/* The following definitions are for hash chains and free
list chain in the index file */
#define PTR_SZ 6 /* size of ptr field in hash chain */
#define PTR_MAX 999999 /* max file offset = 10**PTR_SZ - 1 */
#define NHASH_DEF 137 /* default hash table size */
#define FREE_OFF 0 /* free list offset in index file */
#define HASH_OFF PTR_SZ /* hash table offset in index file */
typedef unsigned long DBHASH; /* hash values */
typedef unsigned long COUNT; /* unsigned counter */
/* 记录打开数据的所有信息, db_open函数返回DB结构的指针DBHANDLE值 */
typedef struct
{
int idxfd; /* fd for index file */
int datfd; /* fd for data file */
char *idxbuf; /* malloc'ed buffer for index record */
char *datbuf; /* malloc'ed buffer for data record */
char *name; /* name db was opend under */
off_t idxoff; /* offset in index file of index record */
/* key is at (idxoff + PTR_SZ + IDXLEN_SZ) */
size_t idxlen; /* length of index record */
/* excludes IDXLEN_SZ bytes at front of record */
off_t datoff; /* offset in data file of data record */
size_t datlen; /* length of data record */
/* includes newline at end */
off_t ptrval; /* contents of chain ptr in index record */
off_t ptroff; /* chain ptr offset pointing to this idx record */
off_t chainoff; /* offset of hash chain for this index record */
off_t hashoff; /* offset in index file of hash table */
DBHASH nhash; /* current hash table size */
/* 对成功和不成功的操作计数 */
COUNT cnt_delok; /* delete OK */
COUNT cnt_delerr; /* delete error */
COUNT cnt_fetchok; /* fetch OK */
COUNT cnt_fetcherr; /* fetch error */
COUNT cnt_nextrec; /* nextrec */
COUNT cnt_stor1; /* store: DB_INSERT, no empty, appended */
COUNT cnt_stor2; /* store: DB_INSERT, found empty, reused */
COUNT cnt_stor3; /* store: DB_REPLACE, diff len, appended */
COUNT cnt_stor4; /* store: DB_REPLACE, same len, overwrote */
COUNT cnt_storerr; /* store error */
}DB;
/* 内部私有函数。声明为static,只有同一文件中的其他函数才能调用 */
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);
/* 如果调用者想要创建数据库文件,那么用可选择的第3个参数指定文件权限。
db_open打开索引文件和数据文件,在必要时初始化索引文件。
成功返回数据库句柄,出错返回NULL。
如果成功返回,则将建立两个文件: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];
struct stat statbuff;
/* 为DB结构分配空间 */
len = strlen(pathname);
if((db = _db_alloc(len)) == NULL)
err_dump("db_open: _db_alloc error for DB");
db->nhash = NHASH_DEF; /* hash table size */
db->hashoff = HASH_OFF; /* offset in index file of hash table */
strcpy(db->name, pathname);
strcat(db->name, ".idx"); /* 数据库索引文件名 */
if(oflag & O_CREAT)
{
va_list ap;
/* 如果想创建数据库文件,找到可选的第3个参数 */
va_start(ap, oflag);
mode = va_arg(ap, int);
va_end(ap);
db->idxfd = open(db->name, oflag, mode);
strcpy(db->name + len, ".dat");
db->datfd = open(db->name, oflag, mode);
}
else
{
db->idxfd = open(db->name, oflag);
strcpy(db->name + len, ".dat");
db->datfd = open(db->name, oflag);
}
/* 如果打开或创建任意数据库文件时出错 */
if(db->idxfd < 0 || db->datfd < 0)
{
_db_free(db); /* 清除DB结构 */
return (NULL);
}
if((oflag & (O_CREAT | O_TRUNC)) == (O_CREAT | O_TRUNC))
{
/*
如果正在建立数据库,则必须正确地加锁
Write lock the entire file so that we can stat it,
check its size, and initialize it, atomically
*/
if(writew_lock(db->idxfd, 0, SEEK_SET, 0) < 0)
err_dump("db_open: writew_lock error");
if(fstat(db->idxfd, &statbuff) < 0)
err_sys("db_open: fstat error");
/*
如果索引文件长度为0,那么该文件是刚刚被创建的,
所以需初始化它所包含的空闲链表和散列链表指针。
*/
if(statbuff.st_size == 0)
{
/*
字符串%*d将数据库指针从整型变为ASCII字符串,
这一格式告诉sprintf取PTR_SZ参数,用它作为下一个参数的最小字段宽度,此处为0(因为
正在创建一个新的数据库)。其作用是强迫创建的字符串至少包含PTR_SZ个字符(在左边用
空格填充。在_db_writeidx和_db_writeptr中,将传送一个非0值,但首先将验证指针指不
大于PTR_MAX,以保证写入数据库的指针字符串恰好为PTR_SZ(6)个字符。
*/
sprintf(asciiptr, "%*d", PTR_SZ, 0);
/* 构造散列表,并写到索引文件中 */
hash[0] = 0;
for(i = 0; i< NHASH_DEF+1; i++)
strcat(hash, asciiptr);
strcat(hash, "\n");
i = strlen(hash);
if(write(db->idxfd, hash, i) != i)
err_dump("db_open: index file init write error");
}
/* 解锁索引文件 */
if(un_lock(db->idxfd, 0, SEEK_SET, 0) < 0)
err_dump("db_open: un_lock error");
}
/* 清除数据库文件指针 */
db_rewind(db);
/* 返回DB结构指针作为句柄,以边调用者以后用于其他数据库函数 */
return (db);
}
/* db_open调用_db_alloc为DB结构分配空间,包括一个索引缓冲和一个数据缓冲 */
static DB *_db_alloc(int namelen)
{
DB *db;
/* 为DB分配存储区,并将该区各单元全部置初值为0 */
if((db = calloc(1, sizeof(DB))) == NULL)
err_dump("_db_alloc: calloc error for DB");
db->idxfd = db->datfd = -1; /* 重新置为-1,以表示它们至此还不是有效的 */
/*
Allocate room for the name.
+5 for ".idx" or ".dat" plus null at end
*/
if((db->name = malloc(namelen + 5)) == NULL)
err_dump("_db_alloc: malloc error for name");
/*
Allocate an index buffer and a data buffer.
+2 for newline and null at end.
*/
if((db->idxbuf = malloc(IDXLEN_MAX + 2)) == NULL)
err_dump("_db_alloc: malloc error for index buffer");
if((db->datbuf = malloc(DATLEN_MAX + 2)) == NULL)
err_dump("_db_alloc: malloc error for data buffer");
return (db);
}
/* Relinquish access to the database */
void db_close(DBHANDLE h)
{
_db_free((DB *) h); /* close fds, free buffers * struct */
}
/* db_open在打开索引文件和数据文件时如果发生错误,则调用_db_free释放资源;
应用程序在结束对数据库的使用后,db_close也调用_db_free。 */
static void _db_free(DB *db)
{
/* 关闭文件 */
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);
/* 释放DB结构占用的存储区 */
free(db);
}
/* 根据给定的键读取一条记录 */
char *db_fetch(DBHANDLE h, const char *key)
{
DB *db = h;
char *ptr;
/* 查找该记录 */
if(_db_find_and_lock(db, key, 0) < 0)
{
ptr = NULL;
db->cnt_fetcherr++;
}
else /* 成功 */
{
ptr = _db_readdat(db); /* 读相应的记录 */
db->cnt_fetchok++;
}
if(un_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0)
err_dump("db_fetch: un_lock error");
return (ptr);
}
/* 在函数库内部按给定的键查找记录 */
/* 如果想在索引文件上加一把写锁,则将writelock参数设置为非0;否则加读锁 */
static int _db_find_and_lock(DB *db, const char *key, int writelock)
{
off_t offset, nextoffset;
/* 将键变为散列值,用其计算在文件中相应散列链的起始地址(chainoff)*/
db->chainoff = (_db_hash(db, key) * PTR_SZ) + db->hashoff;
db->ptroff = db->chainoff;
/* 只锁该散列链开始处的第1个字节,允许多个进程同时搜索不同的
散列链,因此增加了并发性 */
if(writelock)
{
if(writew_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0)
err_dump("_db_find_and_lock: writew_lock error");
}
else
{
if(readw_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0)
err_dump("_db_find_and_lock: readw_lock error");
}
/* 读散列链中的第一个指针, 如果返回0,该散列链为空 */
offset = _db_readptr(db, db->ptroff);
while(offset != 0)
{
/* 读取每条索引记录。将当前记录的键填入DB结构的idxbuf字段。
如果_db_readidx返回0,则已打到散列链的最后一个记录项。*/
nextoffset = _db_readidx(db, offset);
if(strcmp(db->idxbuf, key) == 0)
break; /* found a match */
db->ptroff = offset; /* offset of this (unequal) record */
offset = nextoffset; /* next one to compare */
}
/* offset为0,那么已到散列链末端并且没有找到匹配键 */
return (offset == 0 ? -1 : 0);
}
/* 根据给定的键计算散列值 */
static DBHASH _db_hash(DB *db, const char *key)
{
DBHASH hval = 0;
char c;
int i;
/* 将键中的每个ASCII字符乘以这个字符在字符串中以1开始的索引号,
将这些结果相加,除以散列表记录项数,将余数作为键的散列值。
散列表记录项数为137,是一个素数,提供良好的分布特性。 */
for(i=1; (c = *key++) != 0; i++)
hval += c * i;
return (hval % db->nhash);
}
/*
读取以下三种不同链表指针的任意一种:
1、索引文件中指向空闲链表的第一条索引记录的指针
2、散列表中指向散列链的第一条索引记录的指针
3、存放在每条索引记录开始处、指向下一条记录的指针
调用前,需加锁
*/
static off_t _db_readptr(DB *db, off_t offset)
{
char asciiptr[PTR_SZ + 1];
if(lseek(db->idxfd, offset, SEEK_SET) == -1)
err_dump("_db_readptr: lseek error to ptr field");
if(read(db->idxfd, asciiptr, PTR_SZ) != PTR_SZ)
err_dump("_db_readptr: read error of ptr field");
asciiptr[PTR_SZ] = 0; /* null terminate */
return (atol(asciiptr)); /* 将指针从ASCII形式变换为长整型 */
}
/*
从索引文件的指定偏移量处读取索引记录。
如果成功,返回链表中下一条记录的偏移量,并填充DB结构的许多字段:
idxoff:索引文件中当前记录的偏移量
ptrval:在散列链表中下一索引项的偏移量
idxlen:当前索引记录的长度
idxbuf:实际索引记录
datoff:数据文件中该记录的偏移量
datlen:该数据记录的长度
*/
static off_t _db_readidx(DB *db, off_t offset)
{
ssize_t i;
char *ptr1, *ptr2;
char asciiptr[PTR_SZ + 1], asciilen[IDXLEN_SZ + 1];
struct iovec iov[2];
/*
按调用者提供的参数,查找索引文件偏移量,并记录在DB结构中。
为此即使调用者想要在当前文件偏移处读记录,仍需要调用lseek确定当前偏移量。
因为在索引文件中,索引记录决不会放在偏移记录为0处,所以可用0表示“从当前偏移量处读”
*/
if((db->idxoff = lseek(db->idxfd, offset, offset == 0 ?
SEEK_CUR : SEEK_SET)) == -1)
err_dump("_db_readidx: lseek error");
/*
调用readv读在索引记录开始处的两个定长字段:指向下一条索引记录的链表指针
和该索引记录余下部分的长度(余下部分是不定长的)
*/
iov[0].iov_base = asciiptr;
iov[0].iov_len = PTR_SZ;
iov[1].iov_base = asciilen;
iov[1].iov_len = IDXLEN_SZ;
if((i = readv(db->idxfd, &iov[0], 2)) != PTR_SZ + IDXLEN_SZ)
{
if(i == 0 && offset == 0)
return (-1); /* EOF for db_nextrec */
err_dump("_db_readidx: readv error of index record");
}
/* This is our return value; always >= 0 */
asciiptr[PTR_SZ] = 0; /* null terminate */
db->ptrval = atol(asciiptr); /* offset of next key in chain */
asciilen[IDXLEN_SZ] = 0; /* null terminate */
if((db->idxlen = atoi(asciilen)) < IDXLEN_MIN ||
db->idxlen > IDXLEN_MAX)
err_dump("_db_readidx: invalid length");
/* 将索引记录的不定长部分读入DB的idxbuf字段 */
if((i = read(db->idxfd, db->idxbuf, db->idxlen)) != db->idxlen)
err_dump("_db_readidx: read error of index record");
if(db->idxbuf[db->idxlen-1] != NEWLINE) /* 该记录应以换行符结束 */
err_dump("_db_readidx: missing newline");
db->idxbuf[db->idxlen-1] = 0; /* 将换行符替换为NULL */
/* 索引记录划分为三个字段:键、对应数据记录的偏移量和数据记录的长度 */
if((ptr1 = strchr(db->idxbuf, SEP)) == NULL)
err_dump("_db_readidx: missing first separator");
*ptr1++ = 0; /* replace SEP with null */
if((ptr2 = strchr(ptr1, SEP)) == NULL)
err_dump("_db_readidx: missing second separator");
*ptr2++ = 0;
if(strchr(ptr2, SEP) != NULL)
err_dump("_db_readidx: too many separators");
/* 将数据记录的偏移量和长度变为整型 */
if((db->datoff = atol(ptr1)) < 0)
err_dump("_db_readidx: starting offset < 0");
if((db->datlen = atol(ptr2)) <= 0 || db->datlen > DATLEN_MAX)
err_dump("_db_readidx: invalid length");
return (db->ptrval); /* 散列链中的下一条记录的偏移量 */
}
/* 在datoff和datlen已获正确值后,_db_readdat将数据记录
的内容读入DB结构中的datbuf字段指向的缓冲区 */
static char *_db_readdat(DB *db)
{
if(lseek(db->datfd, db->datoff, SEEK_SET) == -1)
err_dump("_db_readdat: lseek error");
if(read(db->datfd, db->datbuf, db->datlen) != db->datlen)
err_dump("_db_readdat: read error");
if(db->datbuf[db->datlen-1] != NEWLINE)
err_dump("_db_readdat: missing newline");
db->datbuf[db->datlen-1] = 0;
return (db->datbuf);
}
/* 删除与给定键匹配的一条记录 */
int db_delete(DBHANDLE h, const char *key)
{
DB *db = h;
int rc = 0;
/* 判断在数据库中该记录是否存在
如果存在,调用_db_dodelete执行删除该记录的操作
第3个参数控制对散列链是加读锁还是写锁
因为可能执行删除该记录的操作,所以要加一把写锁 */
if(_db_find_and_lock(db, key, 1) == 0)
{
_db_dodelete(db);
db->cnt_delok++;
}
else
{
rc = -1;
db->cnt_delerr++;
}
/* 不管是否找到所需记录,都除去这把锁 */
if(un_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0)
err_dump("db_delete: un_lock error");
return (rc);
}
/* 执行从数据库删除一条记录的所有操作。
此函数的大部分工作仅仅是更新两个链表:空闲链表以及与
键对应的散列链。当一条记录被删除后,将其键和数据记录设为空。*/
static void _db_dodelete(DB *db)
{
int i;
char *ptr;
off_t freeptr, saveptr;
/* Set data buffer and key to all blanks */
for(ptr = db->datbuf, i = 0; i < db->datlen - 1; i++)
*ptr++ = SPACE;
*ptr = 0;
ptr = db->idxbuf;
while(*ptr)
*ptr++ = SPACE;
/* We have to lock the free list */
if(writew_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0)
err_dump("_db_dodelete: writew_lock error");
/* Write the data record with all blanks */
_db_writedat(db, db->datbuf, db->datoff, SEEK_SET);
/* 读空闲链表指针。让这条记录成为空闲链表的第一条记录 */
freeptr = _db_readptr(db, FREE_OFF);
/* 被_db_writeidx修改之前先保存散列链中的当前记录 */
saveptr = db->ptrval;
/* 用被删除的索引记录的偏移量更新空闲链表指针,
也就使其指向当前删除的这条记录, 从而将该被删除记录加到了空闲链表之首 */
_db_writeidx(db, db->idxbuf, db->idxoff, SEEK_SET, freeptr);
/* write the new free list pointer */
_db_writeptr(db, FREE_OFF, db->idxoff);
/* 修改散列链中前一条记录的指针,使其指向正删除记录之后的一条记录,
这样便从散列链中撤除了要删除的记录 */
_db_writeptr(db, db->ptroff, saveptr);
/* 对空闲链表解锁 */
if(un_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0)
err_dump("_db_dodelete: un_lock error");
}
/* 写一条数据记录, 当删除一条记录是,清空数据记录 */
static void _db_writedat(DB *db, const char *data, off_t offset, int whence)
{
struct iovec iov[2];
static char newline = NEWLINE;
/* If we're appending, we have to lock before dong the lseek and
write to make the two an atomic operation. If we're overwriting
an existing record, we don't have to lock */
if(whence == SEEK_END)
if(writew_lock(db->datfd, 0, SEEK_SET, 0) < 0)
err_dump("_db_writedat: writew_lock error");
if((db->datoff = lseek(db->datfd, offset, whence)) == -1)
err_dump("_db_writedat: lseek error");
db->datlen = strlen(data) + 1; /* datlen includes new line */
/* 不能想当然地认为调用者缓冲区尾端有空间可以加换行符,
所以先将换行符送入另一个缓冲,然后再从该缓冲写到数据记录 */
iov[0].iov_base = (char *)data;
iov[0].iov_len = db->datlen - 1;
iov[1].iov_base = &newline;
iov[1].iov_len = 1;
if(writev(db->datfd, &iov[0], 2) != db->datlen)
err_dump("_db_writedat: writev error of dat record");
if(whence == SEEK_END)
if(un_lock(db->datfd, 0, SEEK_SET, 0) < 0)
err_dump("_db_writedat: un_lock error");
}
/* 写一条索引记录
_db_writedat is called before this function to set datoff and datlen
fields in the DB structure, which we need to write the index record */
static void _db_writeidx(DB *db, const char *key, off_t offset,
int whence, off_t ptrval)
{
struct iovec iov[2];
char asciiptrlen[PTR_SZ + IDXLEN_SZ + 1];
int len;
char *fmt;
if((db->ptrval = ptrval) < 0 || ptrval > PTR_MAX)
err_quit("_dbwriteidx: invalid ptr: %d", ptrval);
if(sizeof(off_t) == sizeof(long long))
fmt = "%s%c%lld%c%d\n";
else
fmt = "%s%c%ld%c%d\n";
sprintf(db->idxbuf, fmt, key, SEP, db->datoff, SEP, db->datlen);
if((len = strlen(db->idxbuf)) < IDXLEN_MIN || len > IDXLEN_MAX)
err_dump("_db_writeidx: invalid length");
sprintf(asciiptrlen, "%*ld%*d", PTR_SZ, ptrval, IDXLEN_SZ, len);
/* If we're appending, we have to lock before dong the lseek
and write to make the two an atomic operation */
if(whence == SEEK_END)
if(writew_lock(db->idxfd, ((db->nhash+1)*PTR_SZ) + 1,
SEEK_SET, 0) < 0)
err_dump("_db_writeidx: writew_lock error");
/* 设置索引文件偏移量,从此处开始写索引记录,
将该偏移量存入DB结构的idxoff字段 */
if((db->idxoff = lseek(db->idxfd, offset, whence)) == -1)
err_dump("_db_writeidx: lseek error");
iov[0].iov_base = asciiptrlen;
iov[0].iov_len = PTR_SZ + IDXLEN_SZ;
iov[1].iov_base = db->idxbuf;
iov[1].iov_len = len;
if(writev(db->idxfd, &iov[0], 2) != PTR_SZ + IDXLEN_SZ + len)
err_dump("_db_writeidx: writev error of index record");
if(whence == SEEK_END)
if(un_lock(db->idxfd, ((db->nhash+1)*PTR_SZ)+1,
SEEK_SET, 0) < 0)
err_dump("_db_writeidx: un_lock error");
}
/* 将一个链表指针写到索引文件 */
static void _db_writeptr(DB *db, off_t offset, off_t ptrval)
{
char asciiptr[PTR_SZ + 1];
/* 验证该指针再索引文件的边界范围内,然后将它变成ASCII字符串 */
if(ptrval < 0 || ptrval > PTR_MAX)
err_quit("_db_writeptr: invalid ptr: %d", ptrval);
sprintf(asciiptr, "%*ld", PTR_SZ, ptrval);
/* 按指定的偏移量在索引文件中定位,接着将该指针ASCII字符串写入索引文件 */
if(lseek(db->idxfd, offset, SEEK_SET) == -1)
err_dump("_db_writeptr: lseek error to ptr field");
if(write(db->idxfd, asciiptr, PTR_SZ) != PTR_SZ)
err_dump("_db_writeptr: write error of ptr field");
}
/* 将一条记录添加到数据库 */
int db_store(DBHANDLE h, const char *key, const char *data, int flag)
{
DB *db = h;
int rc, keylen, datlen;
off_t ptrval;
/* 首先验证flag,然后查明数据记录长度是否有效,无效则构造core文件退出 */
if(flag != DB_INSERT && flag != DB_REPLACE && flag != DB_STORE)
{
errno = EINVAL;
return (-1);
}
keylen = strlen(key);
datlen = strlen(data) + 1;
if(datlen < DATLEN_MIN || datlen > DATLEN_MAX)
err_dump("db_store: invalid data length");
/* 查看该记录是否已经存在。如果记录不存在且指定标志位DB_INSERT或DB_STORE,
或者记录存在且指定标志位DB_REPLACE或DB_STORE,均合法
因为db_store可能会修改散列链,所以用_db_find_and_lock的最后一个参数指明要对
散列链加写锁 */
if(_db_find_and_lock(db, key, 1) < 0) /* record not found */
{
if(flag == DB_REPLACE)
{
rc = -1;
db->cnt_storerr++;
errno = ENOENT; /* error, record does not exist */
goto doreturn;
}
/* _db_find_and_lock locked the hash chain for us;
read the chain ptr to the first index record on hash chain */
ptrval = _db_readptr(db, db->chainoff);
if(_db_findfree(db, keylen, datlen) < 0)
{
/* 没有在空闲链表中找到键长度和数据长度与keylen和datlen相同的记录,
需将这条新记录添加到索引文件和数据文件的末尾 */
_db_writedat(db, data, 0, SEEK_END); /* 写数据 */
_db_writeidx(db, key, 0, SEEK_END, ptrval); /* 写索引 */
/* 将新记录加到对应的散列链的链首 */
_db_writeptr(db, db->chainoff, db->idxoff);
db->cnt_stor1++; /* 计数器加1 */
}
else
{
/* 找到了键长度和数据长度与keylen和datlen相同的记录,将这条空记
录从空闲链表上移下来,写入新的索引记录和数据记录,将新记录加到
对应的散列链的链首,并将此种情况的计数器加1 */
_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 /* 具有相同键的记录在数据库中已存在 */
{
if(flag == DB_INSERT)
{
rc = 1;
db->cnt_storerr++;
goto doreturn;
}
/* 替换一条现存的记录,而新数据记录的长度与已存在记录的长度不一样 */
if(datlen != db->datlen)
{
/* 删除旧记录, 将该删除记录存放在空闲链表的链首 */
_db_dodelete(db);
/* Reread the chain ptr in the hash table
(it may change with the deletion) */
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++; /* 此种情况的计数器加1 */
}
else /* 替换一条记录,而新数据记录的长度与已存在的记录的长度恰好一样 */
{
/* 只需重写数据记录,并将计数器加1 */
_db_writedat(db, data, db->datoff, SEEK_SET);
db->cnt_stor4++;
}
}
rc = 0; /* OK */
doreturn:
if(un_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0)
err_dump("db_store: unclock error");
return (rc);
}
/* 试图找到一个指定大小的空闲索引记录和相关联的数据记录 */
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)
err_dump("_db_findfree: writew_lock error");
/* 得到空闲链表头指针 */
saveoffset = FREE_OFF;
offset = _db_readptr(db, saveoffset);
/* 循环遍历空闲链表 */
while(offset != 0)
{
nextoffset = _db_readidx(db, offset);
if(strlen(db->idxbuf) == keylen && db->datlen == datlen)
break; /* found a match */
saveoffset = offset;
offset = nextoffset;
}
if(offset == 0) /* 未找到所要求的键长度和数据长度的可用记录 */
{
rc = -1;
}
else
{
/* 否则,将已找到记录的下一个链表指针值写到前一记录的链表指针,
这样就从空闲链表中移除了该记录 */
_db_writeptr(db, saveoffset, db->ptrval);
rc = 0;
}
/* 结束对空闲链表的操作,释放写锁 */
if(un_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0)
err_dump("_db_findfree: un_lock error");
return (rc); /* 返回状态码 */
}
/* 把数据库重置到“起始状态” */
void db_rewind(DBHANDLE h)
{
DB *db = h;
off_t offset;
/* 将索引文件的文件偏移量定位在索引文件的第一条索引记录
(紧跟在散列表之后) */
offset = (db->nhash + 1) * PTR_SZ;
if((db->idxoff = lseek(db->idxfd, offset+1, SEEK_SET)) == -1)
err_dump("db_rewind: lseek error");
}
/* 返回数据库的下一条记录,返回值是指向数据缓冲的指针
如果key非空,则相应的键复制到该缓冲中 */
char *db_nextrec(DBHANDLE h, char *key)
{
DB *db = h;
char c;
char *ptr;
/* 对空闲链表加读锁,使得正在读该链表时,其他进程不能删除 */
if(readw_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0)
err_dump("db_nextrec: readw_lock error");
do
{
if(_db_readidx(db, 0) < 0) /* 读下一条记录 */
{
ptr = NULL;
goto doreturn;
}
ptr = db->idxbuf;
/* 可能会读到已删除记录,仅返回有效记录,所以跳过全空格记录 */
while((c = *ptr++) != 0 && c == SPACE);
}while(c == 0);
if(key != NULL) /* 找到一个 有效键 */
strcpy(key, db->idxbuf);
/* 读数据记录,返回值未指向包含数据记录的内部缓冲的指针值 */
ptr = _db_readdat(db);
db->cnt_nextrec++;
doreturn:
if(un_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0)
err_dump("db_nextrec: un_lock error");
return (ptr);
}