《unix环境高级编程》--- 数据库函数库

讲解: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);
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值