函数db_fetch为用户接口,根据传入的键值寻找对应的数据,如果找到,则返回指向数据的指针,否则返回NULL,代码如下:
/* 根据给定的键读取一条记录 */ char *db_fetch(DBHANDLE h, const char *key) { DB *db = h; char *ptr; /* 参数三:0表示读,1表示写,根据此函数决定加什么锁 */ if (_db_find_and_lock(db, key, 0) < 0) { /* 返回-1表示未找到记录 */ ptr = NULL; db->cnt_fetcherr++; } else /* 返回0表示查找成功 */ { ptr = _db_readdat(db); /* 返回找到的数据 */ db->cnt_fetchok++; } if (un_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0) { printf("%d\n", __LINE__); exit(-1); } return ptr; }
函数_db_find_and_lock是关键所在,下面是它的代码:
/* writelock:0表示读,1表示写 * 返回值:0查找成功,-1查找失败 */ static int _db_find_and_lock(DB *db, const char *key, int writelock) { off_t offset, nextoffset; /* 根据散列函数定位桶的起始字符,这里要跳过第一个空闲链表 */ db->chainoff = (_db_hash(db, key) * PTR_SZ) + db->hashoff; db->ptroff = db->chainoff; /* 同样指向某个桶 */ if (writelock) /* 写锁 */ { /* 这里只锁一个链的开头一个字节,其它链仍然可用 */ if (writew_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0) { printf("%d\n", __LINE__); exit(-1); } } else /* 读锁 */ { /* 这里只锁一个链的开头一个字节,其它链仍然可用 */ if (readw_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0) { printf("%d\n", __LINE__); exit(-1); } } /* 知道偏移量,知道指针宽度,atol将指针转换成数字类型 */ offset = _db_readptr(db, db->ptroff); /* 直接读取桶中的元素,即第一个节点的偏移量 */ /* 沿着桶向下查找 */ while (offset != 0) /* 如果为0则该散列链为空 */ { nextoffset = _db_readidx(db, offset); /* 读取一条索引记录存入db->idxbuf */ /* db->idxbuf = 键值 '\0' 数据记录偏移量 '\0' 数据记录长度 '\0' */ if (strcmp(db->idxbuf, key) == 0) break; db->ptroff = offset; /* ptroff记录前一索引记录的地址 */ offset = nextoffset; } return (offset == 0 ? -1 : 0); /* offset = 0则没有找到记录 */ }
/* 散列函数 */ static DBHASH _db_hash(DB *db, const char *key) { DBHASH hval = 0; char c; int i; /* 以下为散列函数 */ for (i = 1; (c = *key++) != 0; i++) hval += c * i; return (hval % db->nhash); /* db->nhash = 137 */ }
/* 读取一个指针并转换为数值型,凡是要获取指针值都要调用这个函数 * 根据offset知道指针所在位置,指针所占字节宽度也已知,就能读取一个指针的值了 * 注意:此函数未进行加锁操作,需要手动进行 */ static off_t _db_readptr(DB *db, off_t offset) { char asciiptr[PTR_SZ + 1]; if (lseek(db->idxfd, offset, SEEK_SET) == -1) { printf("%d\n", __LINE__); exit(-1); } if (read(db->idxfd, asciiptr, PTR_SZ) != PTR_SZ) /* PTR_SZ = 6,一次读6个字符 */ { printf("%d\n", __LINE__); exit(-1); } asciiptr[PTR_SZ] = 0; /* 追加一个空字符,为了atol函数辨识结尾处 */ /* atol会跳过前面的空格,遇到非数字或字符串结束时停止转换 */ return (atol(asciiptr)); }
接下来是_db_readidx函数:
/* 根据索引记录的偏移值offset读取一条索引记录,填充DB结构许多成员 * 返回值:下一条记录的偏移量 */ 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]; /* 保存当前索引记录偏移量 * 如果offset为0,表示从当前偏移量处读 */ if ((db->idxoff = lseek(db->idxfd, offset, offset == 0 ? SEEK_CUR : SEEK_SET)) == -1) { printf("%d\n", __LINE__); exit(-1); } /* 散布读,第一部分放asciiptr中,第二部分放asciilen中 */ iov[0].iov_base = asciiptr; iov[0].iov_len = PTR_SZ; /* 6 */ iov[1].iov_base = asciilen; iov[1].iov_len = IDXLEN_SZ; /* 4 */ if ((i = readv(db->idxfd, &iov[0], 2)) != PTR_SZ + IDXLEN_SZ) { /* 返回读取的字节数,结尾返回-1 */ if (i == 0 && offset == 0) return -1; /* 这个返回值是给函数db_nextrec使用的,_db_find_and_lock永远不可能返回-1 */ else { printf("%d\n", __LINE__); exit(-1); } } /* 下一条索引记录偏移量 */ asciiptr[PTR_SZ] = 0; db->ptrval = atol(asciiptr); /* 当前索引记录长度 */ asciilen[IDXLEN_SZ] = 0; if ((db->idxlen = atoi(asciilen)) < IDXLEN_MIN || db->idxlen > IDXLEN_MAX) { /* 索引记录长度必须在6~1024字节之间 */ printf("%d\n", __LINE__); exit(-1); } /* 下面读取实际的索引记录,文件指针已经在调用readv后指向正确位置,即key开头 */ if ((i = read(db->idxfd, db->idxbuf, db->idxlen)) != db->idxlen) { printf("%d\n", __LINE__); exit(-1); } if (db->idxbuf[db->idxlen -1] != NEWLINE) { printf("%d\n", __LINE__); exit(-1); } db->idxbuf[db->idxlen - 1] = 0; /* 把换行符替换为空字符,为了atol函数做准备 */ /* 找出分隔符 */ if ((ptr1 = strchr(db->idxbuf, SEP)) == NULL) { printf("%d\n", __LINE__); exit(-1); } *ptr1++ = 0; /* 分隔符替换为空字符,ptr1现在指向数据记录的偏移量 */ if ((ptr2 = strchr(ptr1, SEP)) == NULL) { printf("%d\n", __LINE__); exit(-1); } *ptr2++ = 0; /* 分隔符替换为空字符,ptr2现在指向数据记录的长度 */ if (strchr(ptr2, SEP) != NULL) /* 只有两个分隔符 */ { printf("%d\n", __LINE__); exit(-1); } /* 保存实际数据的偏移量 */ if ((db->datoff = atol(ptr1)) < 0) /* atol遇到空字符结束 */ { printf("%d\n", __LINE__); exit(-1); } /* 保存实际数据的长度,范围必须在2~1024字节之间 */ if ((db->datlen = atol(ptr2)) <= 0 || db->datlen > DATLEN_MAX) { printf("%d\n", __LINE__); exit(-1); } return (db->ptrval); }
/* 读取实际数据到缓存db->datbuf中 * 返回值:NULL结尾的实际数据 */ static char* _db_readdat(DB *db) { if (lseek(db->datfd, db->datoff, SEEK_SET) == -1) /* 定位 */ { printf("%d\n", __LINE__); exit(-1); } if (read(db->datfd, db->datbuf, db->datlen) != db->datlen) /* 读入缓存 */ { printf("%d\n", __LINE__); exit(-1); } if (db->datbuf[db->datlen - 1] != NEWLINE) /* 数据文件中的一个条必以换行符结尾 */ { printf("%d\n", __LINE__); exit(-1); } db->datbuf[db->datlen - 1] = 0; /* 换行符替换成空字符 */ return (db->datbuf); }