📄 16.htm
字号:
<p>off_t offset, int whence, off_t ptrval) </p>
<p>{ </p>
<p>struct iovec iov[2]; </p>
<p>char asciiptrlen[PTR_SZ + IDXLEN_SZ +1]; </p>
<p>int len; </p>
<p>if ( (db->ptrval = ptrval) < 0 || ptrval > PTR_MAX) </p>
<p>err_quit("invalid ptr: %d", ptrval); </p>
<p>sprintf(db->idxbuf, "%s%c%d%c%d\n", </p>
<p>key, SEP, db->datoff, SEP, db->datlen); </p>
<p>if ( (len = strlen(db->idxbuf)) < IDXLEN_MIN || len > IDXLEN_MAX) </p>
<p>err_dump("invalid length"); </p>
<p>sprintf(asciiptrlen, "%*d%*d", PTR_SZ, ptrval, IDXLEN_SZ, len); </p>
<p>/* If we're appending, we have to lock before doing the lseek() </p>
<p>and write() to make the two an atomic operation. If we're </p>
<p>overwriting an existing record, we don't have to lock. */ </p>
<p>if (whence == SEEK_END) /* we're appending */ </p>
<p>if (writew_lock(db->idxfd, ((db->nhash+1)*PTR_SZ)+1, </p>
<p>SEEK_SET, 0) < 0) </p>
<p>err_dump("writew_lock error"); </p>
<p>/* Position the index file and record the offset */ </p>
<p>if ( (db->idxoff = lseek(db->idxfd, offset, whence)) == -1) </p>
<p>err_dump("lseek error"); </p>
<p>iov[0].iov_base = asciiptrlen; </p>
<p>iov[0].iov_len = PTR_SZ + IDXLEN_SZ; </p>
<p>iov[1].iov_base = db->idxbuf; </p>
<p>iov[1].iov_len = len; </p>
<p>if (writev(db->idxfd, &iov[0], 2) != PTR_SZ + IDXLEN_SZ + len) </p>
<p>err_dump("writev error of index record"); </p>
<p>if (whence == SEEK_END) </p>
<p>if (un_lock(db->idxfd, ((db->nhash+1)*PTR_SZ)+1, SEEK_SET, 0) < 0) </p>
<p>err_dump("un_lock error"); </p>
<p>} </p>
<p>程序16.16 _db_writeidx函数 </p>
<p>_db_dodelete调用的最后一个函数是_db_writeptr(程序16.17)。它被调用 </p>
<p>两次--一次用来写空闲链表指针,一次用来写Hash链表指针(指向被删除索引记录
</p>
<p>的指针)。 </p>
<p>#include "db.h" </p>
<p>/* Write a chain ptr field somewhere in the index file: </p>
<p>* the free list, the hash table, or in an index record. */ </p>
<p>void </p>
<p>_db_writeptr(DB *db, off_t offset, off_t ptrval) </p>
<p>{ </p>
<p>char asciiptr[PTR_SZ + 1]; </p>
<p>if (ptrval < 0 || ptrval > PTR_MAX) </p>
<p>err_quit("invalid ptr: %d", ptrval); </p>
<p>sprintf(asciiptr, "%*d", PTR_SZ, ptrval); </p>
<p>if (lseek(db->idxfd, offset, SEEK_SET) == -1) </p>
<p>err_dump("lseek error to ptr field"); </p>
<p>if (write(db->idxfd, asciiptr, PTR_SZ) != PTR_SZ) </p>
<p>err_dump("write error of ptr field"); </p>
<p>} </p>
<p>程序16.17 _db_writeptr函数 </p>
<p>在程序16.18中列出了我们最长的数据库函数db_store。它从调用_db_find开
</p>
<p>始,以查看这个记录是否存在。如果记录存在且标志为DB_REPLACE,或记录不存在
</p>
<p>且标志为DB_INSERT,这些都是允许的。替换一条已存在的记录,指的是该记录的
</p>
<p>主键一样,而数据很可能不一样。 </p>
<p>注意到因为db_store可能会改变Hash链,所以调用_db_find的最后一条参数说
</p>
<p>明要对Hash链加写锁。 </p>
<p>如果我们要加一条新记录到数据库,我们调用函数_db_findfree(程序16.19
</p>
<p>)在空闲链表中搜索一个有同样主键大小和同样数据大小的已删除的记录。
</p>
<p>_db_findfree中的while循环遍历空闲链表以搜寻一个有对应主键大小和数据
</p>
<p>大小的索引记录项。在我们这个简单的实现中,只有当一个已删除记录的的主键大
</p>
<p>小及数据大小与新加入的记录的主键大小及数据大小一样时才重用已删除的记录的
</p>
<p>空间。其它更好的算法一般更复杂。 </p>
<p>_db_findfree需要对空闲链表加写锁以避免与其它使用空闲链表的进程互相影
</p>
<p>响。当一条记录从空闲链表上取下来后,就可以打开这个写锁。_db_dodelete也要
</p>
<p>修改空闲链表。 </p>
<p>#include "db.h" </p>
<p>/* Store a record in the database. </p>
<p>* Return 0 if OK, 1 if record exists and DB_INSERT specified, </p>
<p>* -1 if record doesn't exist and DB_REPLACE specified. */ </p>
<p>int </p>
<p>db_store(DB *db, const char *key, const char *data, int flag) </p>
<p>{ </p>
<p>/* An empty record of the correct size was not found. </p>
<p>We have to append the new record to the ends of </p>
<p>the index and data files */ </p>
<p>_db_writedat(db, data, 0, SEEK_END); </p>
<p>_db_writeidx(db, key, 0, SEEK_END, ptrval); </p>
<p>/* db->idxoff was set by _db_writeidx(). The new </p>
<p>record goes to the front of the hash chain. */ </p>
<p>_db_writeptr(db, db->chainoff, db->idxoff); </p>
<p>db->cnt_stor1++; </p>
<p>} else { </p>
<p>/* We can reuse an empty record. </p>
<p>_db_findfree() removed the record from the free </p>
<p>list and set both db->datoff and db->idxoff. */ </p>
<p>_db_writedat(db, data, db->datoff, SEEK_SET); </p>
<p>_db_writeidx(db, key, db->idxoff, SEEK_SET, ptrval); </p>
<p>/* reused record goes to the front of the hash chain. */ </p>
<p>_db_writeptr(db, db->chainoff, db->idxoff); </p>
<p>db->cnt_stor2++; </p>
<p>} </p>
<p>} else { /* record found */ </p>
<p>if (flag & DB_INSERT) { </p>
<p>rc = 1; </p>
<p>db->cnt_storerr++; </p>
<p>goto doreturn; /* error, record already in db */ </p>
<p>} </p>
<p>/* We are replacing an existing record. We know the new </p>
<p>key equals the existing key, but we need to check if </p>
<p>the data records are the same size. */ </p>
<p>if (datlen != db->datlen) { </p>
<p>_db_dodelete(db); /* delete the existing record */ </p>
<p>/* Reread the chain ptr in the hash table </p>
<p>(it may change with the deletion). */ </p>
<p>ptrval = _db_readptr(db, db->chainoff); </p>
<p>/* append new index and data records to end of files */ </p>
<p>_db_writedat(db, data, 0, SEEK_END); </p>
<p>_db_writeidx(db, key, 0, SEEK_END, ptrval); </p>
<p>/* new record goes to the front of the hash chain. */ </p>
<p>_db_writeptr(db, db->chainoff, db->idxoff); </p>
<p>db->cnt_stor3++; </p>
<p>} else { </p>
<p>/* same size data, just replace data record */ </p>
<p>_db_writedat(db, data, db->datoff, SEEK_SET); </p>
<p>db->cnt_stor4++; </p>
<p>} </p>
<p>} </p>
<p>rc = 0; /* OK */ </p>
<p>doreturn: /* unlock the hash chain that _db_find() locked */ </p>
<p>if (un_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0) </p>
<p>err_dump("un_lock error"); </p>
<p>return(rc); </p>
<p>} </p>
<p>程序16.18 db_store函数 </p>
<p>#include "db.h" </p>
<p>/* Try to find a free index record and accompanying data record </p>
<p>* of the correct sizes. We're only called by db_store(). */ </p>
<p>int </p>
<p>_db_findfree(DB *db, int keylen, int datlen) </p>
<p>{ </p>
<p>int rc; </p>
<p>off_t offset, nextoffset, saveoffset; </p>
<p>/* Lock the free list */ </p>
<p>if (writew_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0) </p>
<p>err_dump("writew_lock error"); </p>
<p>/* Read the free list pointer */ </p>
<p>saveoffset = FREE_OFF; </p>
<p>offset = _db_readptr(db, saveoffset); </p>
<p>while (offset != 0) { </p>
<p>nextoffset = _db_readidx(db, offset); </p>
<p>if (strlen(db->idxbuf) == keylen && db->datlen == datlen) </p>
<p>break; /* found a match */ </p>
<p>saveoffset = offset; </p>
<p>offset = nextoffset; </p>
<p>} </p>
<p>if (offset == 0) </p>
<p>rc = -1; /* no match found */ </p>
<p>else { </p>
<p>/* Found a free record with matching sizes. </p>
<p>The index record was read in by _db_readidx() above, </p>
<p>which sets db->ptrval. Also, saveoffset points to </p>
<p>the chain ptr that pointed to this empty record on </p>
<p>the free list. We set this chain ptr to db->ptrval, </p>
<p>which removes the empty record from the free list. */ </p>
<p>_db_writeptr(db, saveoffset, db->ptrval); </p>
<p>rc = 0; </p>
<p>/* Notice also that _db_readidx() set both db->idxoff </p>
<p>and db->datoff. This is used by the caller, db_store(), </p>
<p>to write the new index record and data record. */ </p>
<p>} </p>
<p>/* Unlock the free list */ </p>
<p>if (un_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0) </p>
<p>err_dump("un_lock error"); </p>
<p>return(rc); </p>
<p>} </p>
<p>程序16.19 _db_findfree函数 </p>
<p>回到函数db_store,在调用_db_find后,有四种可能: </p>
<p>1. 加入一条新的记录,而_db_findfree没有找到对应大小的空闲记录。这意味着
</p>
<p>我们要将这条新记录添加到索引文件和数据文件的末尾。通过调用_db_writeptr将
</p>
<p>新记录加到对应的Hash链的链首。 </p>
<p>2. 加入一条新的记录,且_db_findfree找到对应大小的空闲记录。这条空闲记录
</p>
<p>被_db_findfree从空闲链表上移下来,新的索引记录和数据记录被写入,然后通过
</p>
<p>调用_db_writeptr将新记录加到对应的Hash链的链首。 </p>
<p>3.
要替换一条记录,而新数据记录的长度与已存在的记录的长度不一样。我们调
</p>
<p>用_db_dodelete将老记录删除,然后将新记录添加到索引文件和数据文件的末尾(
</p>
<p>也可以用其他方法,如我们可以再找一找是否有适当大小的已删除的记录项)。最
</p>
<p>后调用_db_writeptr将新记录加到对应的Hash链的链首。 </p>
<p>4.
要替换一条记录,而新数据记录的长度与已存在的记录的长度恰好一样。这是
</p>
<p>最容易的情况,我们只需要重写记录即可。 </p>
<p>在向文件的末尾添加索引记录或数据记录时,需要加锁(回忆我们在程序12.
</p>
<p>6中遇到的相对文件尾加锁问题)。在上面四种可能的第1和第3种情况,db_store
</p>
<p>调用_db_writeidx和_db_writedat时,第3个参数为0,第4个参数为SEEK_END。这
</p>
<p>里,第4个参数作为一个标志用来告诉这两个函数,新的记录将被添加到文件的末
</p>
<p>尾。_db_writeidx用到的技术是对索引文件加写锁,加锁的范围从Hash链的末尾到
</p>
<p>文件的末尾。这不会影响其他数据库的读用户和写用户(这些用户将对Hash链加锁
</p>
<p>),但如果其他用户此时调用db_store来添加数据则会被锁住。_db_writedat使用
</p>
<p>的方法是对整个数据文件加写锁。同样这也不会影响其他数据库的读用户和写用户
</p>
<p>(它们甚至不对数据文件加锁),但如果其他用户此时调用db_store来向数据文件
</p>
<p>添加数据则会被锁住(见Exercise 16.3)。 </p>
<p>最后两个函数是db_nextrec和db_rewind,这两个函数用来读取数据库的所有
</p>
<p>记录。通常在下列形式的循环中的使用这两个函数: </p>
<p>db_rewind(db) ; </p>
<p>while( (ptr = db_nextrec(db, key)) != NULL) { </p>
<p>/* process record */ </p>
<p>} </p>
<p>我们前面警告过,这里记录的返回没有一定的次序--它们并不按主键的顺序。
</p>
<p>函数db_rewind(程序16.20)将索引文件定位在第一条索引记录(紧跟在Has
</p>
<p>h表后面)。 </p>
<p>#include "db.h" </p>
<p>/* Rewind the index file for db_nextrec(). </p>
<p>* Automatically called by db_open(). </p>
<p>* Must be called before first db_nextrec(). </p>
<p>*/ </p>
<p>void </p>
<p>db_rewind(DB *db) </p>
<p>{ </p>
<p>off_t offset; </p>
<p>offset = (db->nhash + 1) * PTR_SZ; /* +1 for free list ptr */ </p>
<p>/* We're just setting the file offset for this process </p>
<p>to the start of the index records; no need to lock. </p>
<p>+1 below for
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -