TCHDB源码阅读

 

Tokyo Cabinet TCHDB源码阅读——关于变长变量的高效存取(节约存储空间)


在TC中,很多地方为了节约存储空间,在保存变量时不会直接存放变量类型长度的值(比如4字节或8字节的值)到文件中,它会探测变量用来表示值的有效字节数,然后把这些有意义的字节保存起来,在后面读取该变量时,TC会把该变量的所有有效字节都读出来,从而计算出该变量所表示的值。

为了实现这个功能,TC的做法是:把变量的每个字节当成一个有符号数,最高位仅做为符号位使用,前面7位才用来表示真正的值,这样一来,文件中的4个字节的数仅有28位用来保存长度信息,如果我们要保存0xFFFFFFFF这个值,就需要在文件中占用5个字节,然而,这种情况的值毕竟是少数(4字节整型数只有超过2^28时才会在文件中占用5字节,其它情况都会少于或等于4字节),我们一般存放的值都很小,就只需要少于4个字节的存储长度,比如存放小于2^7的所有值只需要1个字节空间,存放2^7到2^14之间的值仅需要2字节等等,通过这中方式节省的空间会远远少于我们浪费的空间,因此这种做法在节省文件空间方面还是很有效的。

上面大概讲了原理,下面我们来看看TC中的代码,看看TC具体怎么实现的,我会在下面的代码中给出注释说明。TC主要通过2个宏来分别完成存放和读取变长变量,这2个宏又分别对应32位版本(最多存取4字节的值)和64位的版本(最多存放8字节的值),我仅分析32位的情况,64位情况类似。

1、  把一个变量按变长方式放到buffer中

/* set a buffer for a variable length number */

#define TCSETVNUMBUF(TC_len, TC_buf, TC_num) / // TC_num为正整数

  do { /

    int _TC_num = (TC_num); /

    if(_TC_num == 0){ /  // 存放的变量为0

      ((signed char *)(TC_buf))[0] = 0; /

      (TC_len) = 1; /  // 返回存放了1个字节长度

    } else { /

      (TC_len) = 0; /

      while(_TC_num > 0){ /  // 变量大于0,就一直循环存放

        int _TC_rem = _TC_num & 0x7f; / // 取变量低7位存放

        _TC_num >>= 7; / // 变量右移7位

        if(_TC_num > 0){ / // 去掉低7位后变量是否还有值?

// 按负数存放取得的变量低7位值,减一确保为负数,因为中间可能有0

          ((signed char *)(TC_buf))[(TC_len)] = -_TC_rem - 1; /         

} else { /  // 保留最后7位,按正数的形式

          ((signed char *)(TC_buf))[(TC_len)] = _TC_rem; /

        } /

        (TC_len)++; / // 存放的长度加1,表示用了多少字节来存放变量

      } /

    } /

  } while(false)

2、  从buffer中读出按变长方式存放的变量值

/* read a variable length buffer */

#define TCREADVNUMBUF(TC_buf, TC_num, TC_step) /

  do { /

    TC_num = 0; /

    int _TC_base = 1; /

    int _TC_i = 0; / // 从buffer第一个成员开始读取

    while(true){ /

      if(((signed char *)(TC_buf))[_TC_i] >= 0){ / // 该字节大于0,则是最后字节

        TC_num += ((signed char *)(TC_buf))[_TC_i] * _TC_base; / // 累加

        break; / // 退出结束

      } /

        // 该字节小于0,则不是最后字节

      TC_num += _TC_base * (((signed char *)(TC_buf))[_TC_i] + 1) * -1; / //累加

      _TC_base <<= 7; /

      _TC_i++; / // 移动buffer

    } /

    (TC_step) = _TC_i + 1; / // 读取长度加1,表示读了多少字节的变量

 

  } while(false)



Tokyo Cabinet TCHDB源码阅读——delayed record pool和tchdbputasyncimpl相关函数代码注释

在TCHDB结构中跟异步写操作相关的成员有以下几个:

 

[cpp]  view plain copy
  1. bool async;         /* whether asynchronous storing is called */  
  2. TCXSTR *drpool;   /* delayed record pool */  
  3. TCXSTR *drpdef;   /* deferred records of the delayed record pool */  
  4. uint64_t drpoff;     /* offset of the delayed record pool */  

 

    从上面的注释可知,DRP有2个缓冲池,其中drpool是我们传统意义上的缓冲池,而drpdef又是针对drpool做了一次缓冲,仅仅看这里的英文注释我感觉有点难理解,后面看了源码我再尝试说说自己对这两个缓冲区的理解。  

    在tchdbputasyncimpl函数中下面这个语句片段初始化DRP(delayed record pool)区:

[cpp]  view plain copy
  1. if(!hdb->drpool){  
  2.    hdb->drpool = tcxstrnew3(HDBDRPUNIT + HDBDRPLAT);  
  3.   
  4.    hdb->drpdef = tcxstrnew3(HDBDRPUNIT);  
  5.   
  6.    hdb->drpoff = hdb->fsiz;  
  7.  }  

      由hdb->drpoff = hdb->fsiz这个语句可知,每次DRP区(由drpool指针指向)中的记录都是从文件末尾开始添加的,再由同步写操作tchdbput函数源代码可知,该函数会在写入记录前检查前面的写入操作是否为异步操作,如果是,则它会刷新异步写缓冲区并删除该异步写缓冲区,从而保证同步写操作执行时,所有的异步写操作都完成,因此,TC中的异步写操作和同步写操作不会交错混杂在一起,这也在一定程度上简化了异步写操作的实现。

下面是tchdbputasyncimpl源码及注释:

[cpp]  view plain copy
  1. /* Store a record in asynchronus fashion. 
  2.    `hdb' specifies the hash database object. 
  3.    `kbuf' specifies the pointer to the region of the key. 
  4.    `ksiz' specifies the size of the region of the key. 
  5.    `bidx' specifies the index of the bucket array. 
  6.    `hash' specifies the hash value for the collision tree. 
  7.    `vbuf' specifies the pointer to the region of the value. 
  8.    `vsiz' specifies the size of the region of the value. 
  9.    If successful, the return value is true, else, it is false. */  
  10. static bool tchdbputasyncimpl(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx,  
  11.                               uint8_t hash, const char *vbuf, int vsiz){  
  12.   assert(hdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);  
  13. // 从缓存中删除指定记录,因为我们在更改该记录,保证数据一致性  
  14.  if(hdb->recc) tcmdbout(hdb->recc, kbuf, ksiz);   
  15.  if(!hdb->drpool){ // drp区还没分配?分配好该区域  
  16.     hdb->drpool = tcxstrnew3(HDBDRPUNIT + HDBDRPLAT);  
  17.     hdb->drpdef = tcxstrnew3(HDBDRPUNIT);  
  18.     hdb->drpoff = hdb->fsiz;  
  19.   }  
  20.   off_t off = tchdbgetbucket(hdb, bidx); // 取得hash桶数组中bidx索引位置记录的文件偏移量  
  21.   off_t entoff = 0;  
  22.   TCHREC rec;  
  23.   char rbuf[HDBIOBUFSIZ];  
  24.   while(off > 0){  
  25.  // 当读到的冲突树中记录偏移量大于hdb->drpoff - hdb->runit时,则把传入的record相关  
  26.  // 信息放到drpdef缓冲区,这里是一个优化。原因我还没想通。  
  27.     if(off >= hdb->drpoff - hdb->runit){  
  28.       TCDODEBUG(hdb->cnt_deferdrp++);  
  29.       TCXSTR *drpdef = hdb->drpdef;  
  30.       TCXSTRCAT(drpdef, &ksiz, sizeof(ksiz));  
  31.       TCXSTRCAT(drpdef, &vsiz, sizeof(vsiz));  
  32.       TCXSTRCAT(drpdef, kbuf, ksiz);  
  33.       TCXSTRCAT(drpdef, vbuf, vsiz);  
  34.       if(TCXSTRSIZE(hdb->drpdef) > HDBDRPUNIT && !tchdbflushdrp(hdb)) return false;  
  35.       return true;  
  36.     }  
  37.     rec.off = off;  
  38.     if(!tchdbreadrec(hdb, &rec, rbuf)) return false// 读取记录到rec,可能过不会读到记录的key和value值  
  39.     if(hash > rec.hash){ // 用传入的二级hash和读到的记录二级hash比较,寻找传入记录的正确位置  
  40.       off = rec.left;  
  41.       entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)); // 这2句调整off和entoff,在冲突树中继续前进  
  42.     } else if(hash < rec.hash){ // 同上  
  43.       off = rec.right;  
  44.       entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)) +  
  45.         (hdb->ba64 ? sizeof(uint64_t) : sizeof(uint32_t));  
  46.     } else { // 二级hash相等,将传入的记录缓存到drpdef缓冲区中,以后刷新缓冲区时再处理  
  47.       TCDODEBUG(hdb->cnt_deferdrp++);  
  48.       TCXSTR *drpdef = hdb->drpdef;  
  49.       TCXSTRCAT(drpdef, &ksiz, sizeof(ksiz));  
  50.       TCXSTRCAT(drpdef, &vsiz, sizeof(vsiz));  
  51.       TCXSTRCAT(drpdef, kbuf, ksiz);  
  52.       TCXSTRCAT(drpdef, vbuf, vsiz);  
  53.       if(TCXSTRSIZE(hdb->drpdef) > HDBDRPUNIT && !tchdbflushdrp(hdb)) return false;  
  54.       return true;  
  55.     }  
  56.   }  
  57.   // 走到这里,说明没有在冲突树中找到二级hash相等的记录,插入的record为二叉树新节点,应放到drpool缓冲区中  
  58.   // 检查entoff,entoff为0表示传入的record为冲突树的根节点  
  59.   if(entoff > 0){  // entoff大于0,说明传入的record不为根,应该成为叶子几点,更新其父节点孩子指针  
  60.     if(hdb->ba64){  
  61.       uint64_t llnum = hdb->fsiz >> hdb->apow;  
  62.       llnum = TCHTOILL(llnum);  
  63.       if(!tchdbseekwrite(hdb, entoff, &llnum, sizeof(uint64_t))) return false;  
  64.     } else {  
  65.       uint32_t lnum = hdb->fsiz >> hdb->apow;  
  66.       lnum = TCHTOIL(lnum);  
  67.       if(!tchdbseekwrite(hdb, entoff, &lnum, sizeof(uint32_t))) return false;  
  68.     }  
  69.   } else {// 新记录文件偏移值写入hash桶数组,偏移值为文件大小,说明从文件尾部开始放  }  
  70.     tchdbsetbucket(hdb, bidx, hdb->fsiz);   
  71.   tchdbdrpappend(hdb, kbuf, ksiz, vbuf, vsiz, hash); // 添加传入的record到drpool缓冲区  
  72.   hdb->rnum++;  
  73.   if(TCXSTRSIZE(hdb->drpool) > HDBDRPUNIT && !tchdbflushdrp(hdb)) return false// 是否需要刷新缓冲区?  
  74.   return true;  
  75. }  

    通过阅读源代码,我大概说下自己对drpool缓冲区和drpdef缓冲区的功能的个人理解:在异步写入模式下,drpool缓冲区中的记录具有以下3个特点:

1)在记录所在的冲突树中,它们的二级hash值和树中任何记录的二级hash都不相同。

2)它们会按put的顺序被添加到数据文件尾部。

3)drpool中存放的记录格式和它们在文件中的格式完全一致,因此在下次写入数据文件的时候不需要额外处理了。

    至于在drpdef缓冲区中的记录,都能在它们对应的冲突树中找到二级hash值相等的记录,并且它们添加到数据文件的方式暂时也没有决定,只有等到刷新缓冲区时调用tchdbputimpl才决定如何写入数据文件。另外,drpdef中存放的是record的key大小、value大小、key值和value值,在写入数据文件时还需要构造才合适的文件记录格式。

 

    下面是tchdbdrpappend函数代码及注释:

[cpp]  view plain copy
  1. /* Append a record to the delayed record pool. 
  2.    `hdb' specifies the hash database object. 
  3.    `kbuf' specifies the pointer to the region of the key. 
  4.    `ksiz' specifies the size of the region of the key. 
  5.    `vbuf' specifies the pointer to the region of the value. 
  6.    `vsiz' specifies the size of the region of the value. 
  7.    `hash' specifies the second hash value. */  
  8. static void tchdbdrpappend(TCHDB *hdb, const char *kbuf, int ksiz, const char *vbuf, int vsiz,  
  9.                            uint8_t hash){  
  10.   assert(hdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);  
  11.   TCDODEBUG(hdb->cnt_appenddrp++);  
  12.   char rbuf[HDBIOBUFSIZ];  
  13.   // rbuf用于构建存放在磁盘上的record,包含magic nuber、hash值、record头、左右孩子指  
  14.   // 针及具体的key和value相关信息等等,下面几条语句分别给这些成员预留空间并赋值。  
  15.   char *wp = rbuf;  
  16.   *(uint8_t *)(wp++) = HDBMAGICREC;  
  17.   *(uint8_t *)(wp++) = hash;  
  18.   if(hdb->ba64){  // 存放左右孩子指针  
  19.     memset(wp, 0, sizeof(uint64_t) * 2);  
  20.     wp += sizeof(uint64_t) * 2;  
  21.   } else {  
  22.     memset(wp, 0, sizeof(uint32_t) * 2);  
  23.     wp += sizeof(uint32_t) * 2;  
  24.   }  
  25.   uint16_t snum; // 此变量临时存放填充区的长度  
  26.   char *pwp = wp;  
  27.   wp += sizeof(snum); // 预留存放填充区长度空间  
  28.   int step;  
  29.   TCSETVNUMBUF(step, wp, ksiz); // 存放ksiz到rbuf中,ksize的长度为变长,step返回ksize占用的长度  
  30.   wp += step;  
  31.   TCSETVNUMBUF(step, wp, vsiz); // 存放vsiz,处理方式和ksiz一样  
  32.   wp += step;  
  33.   int32_t hsiz = wp - rbuf;  
  34.   int32_t rsiz = hsiz + ksiz + vsiz; // 不包含填充区的record长度  
  35.   uint16_t psiz = tchdbpadsize(hdb, hdb->fsiz + rsiz); // record填充区长度  
  36.   hdb->fsiz += rsiz + psiz; // hdb文件长度增加新record的长度  
  37.   snum = TCHTOIS(psiz);  
  38.   memcpy(pwp, &snum, sizeof(snum)); // 填写填充区长度到rbuf预留空间中  
  39.   TCXSTR *drpool = hdb->drpool; // 将新记录追加到drp区  
  40.   TCXSTRCAT(drpool, rbuf, hsiz);  
  41.   TCXSTRCAT(drpool, kbuf, ksiz);  
  42.   TCXSTRCAT(drpool, vbuf, vsiz);  
  43.   if(psiz > 0){ // 同时追加填充区,填充区都置为0  
  44.     char pbuf[psiz];  
  45.     memset(pbuf, 0, psiz);  
  46.     TCXSTRCAT(drpool, pbuf, psiz);  
  47.   }  

 

Tokyo Cabinet TCHDB源码阅读——tchdbput及相关函数代码注释


    tchdbput用于向数据文件写入一条记录(record),写入的模式有很多种,比如覆盖写(overwrite)、追加写(append)、加一个整数、加一个浮点数或者调用回调函数处理,然后把结果当作记录写入等等,这个函数在执行时是直接写入硬盘中的数据文件的,和它对应有一个异步写入的函数,名为tchdbputasync,此函数暂时把记录放于drp(delayed record pool)中,在合适的时候再写回硬盘,我后面会有相应函数分析。下面我贴出tchdbput函数的代码及注释,仅当成个人学习记录:

[cpp]  view plain copy
  1. /* Store a record into a hash database object. */  
  2. bool tchdbput(TCHDB *hdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){  
  3.   assert(hdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);  
  4.   if(!HDBLOCKMETHOD(hdb, false)) return false// 检查是否可获得函数锁  
  5.   uint8_t hash;  
  6.  // 根据key值计算出2个hash值bidx和hash,其中bidx用户索引hash桶数组,hash用于在冲突数中  
  7. //查找记录位置,另外冲突数也在很大程度上依靠hash来获得尽量的平衡  
  8.   uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);  
  9.   if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER)){ // 检查文件是否已打开或当前是否为写模式  
  10.     tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);  
  11.     HDBUNLOCKMETHOD(hdb);  
  12.     return false;  
  13.   }  
  14.   if(hdb->async && !tchdbflushdrp(hdb)){ // 若是处于异步操作模式,则刷新延迟写缓冲区  
  15.     HDBUNLOCKMETHOD(hdb);  
  16.     return false;  
  17.   }  
  18.   if(!HDBLOCKRECORD(hdb, bidx, true)){ // 检查是否可对记录进行操作  
  19.     HDBUNLOCKMETHOD(hdb);  
  20.     return false;  
  21.   }  
  22.   if(hdb->zmode){ // 是否压缩记录,若是,则选择相应压缩算法压缩记录后再存储记录  
  23.     char *zbuf;  
  24.     if(hdb->opts & HDBTDEFLATE){  
  25.       zbuf = _tc_deflate(vbuf, vsiz, &vsiz, _TCZMRAW);  
  26.     } else if(hdb->opts & HDBTBZIP){  
  27.       zbuf = _tc_bzcompress(vbuf, vsiz, &vsiz);  
  28.     } else if(hdb->opts & HDBTTCBS){  
  29.       zbuf = tcbsencode(vbuf, vsiz, &vsiz);  
  30.     } else {  
  31.       zbuf = hdb->enc(vbuf, vsiz, &vsiz, hdb->encop);  
  32.     }  
  33.     if(!zbuf){  
  34.       tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);  
  35.       HDBUNLOCKRECORD(hdb, bidx);  
  36.       HDBUNLOCKMETHOD(hdb);  
  37.       return false;  
  38.     }  
  39.     bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, zbuf, vsiz, HDBPDOVER);  
  40.     TCFREE(zbuf);  
  41.     HDBUNLOCKRECORD(hdb, bidx);  
  42.     HDBUNLOCKMETHOD(hdb);  
  43.     if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&  
  44.        !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;  
  45.     return rv;  
  46.   }  
  47.   bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, vbuf, vsiz, HDBPDOVER); // 调用tchdbputimpl写入记录  
  48.   HDBUNLOCKRECORD(hdb, bidx);  
  49.   HDBUNLOCKMETHOD(hdb);  
  50.   if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&  
  51.      !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;  
  52.   return rv;  
  53. }  
 

  上面的函数中,主要是通过调用tchdbputimpl来实现写入记录功能,该函数如下:

[cpp]  view plain copy
  1. /* Store a record. 
  2.    `hdb' specifies the hash database object. 
  3.    `kbuf' specifies the pointer to the region of the key. 
  4.    `ksiz' specifies the size of the region of the key. 
  5.    `bidx' specifies the index of the bucket array. 
  6.    `hash' specifies the hash value for the collision tree. 
  7.    `vbuf' specifies the pointer to the region of the value. 
  8.    `vsiz' specifies the size of the region of the value. 
  9.    `dmode' specifies behavior when the key overlaps. 
  10.    If successful, the return value is true, else, it is false. */  
  11. static bool tchdbputimpl(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx, uint8_t hash,  
  12.                          const char *vbuf, int vsiz, int dmode){  
  13.   assert(hdb && kbuf && ksiz >= 0);  
  14.   if(hdb->recc) tcmdbout(hdb->recc, kbuf, ksiz); /*从cache中删除记录*/  
  15.   off_t off = tchdbgetbucket(hdb, bidx); // 从hash数组中取得对应记录的偏移量  
  16.   off_t entoff = 0; // 用于查找冲突数,始终存放冲突树中,当前比较记录的父节点在文件中的偏移量  
  17.   TCHREC rec; // 申请了一个记录结构,用于在内存中存放记录的相关信息  
  18.   char rbuf[HDBIOBUFSIZ];  
  19.   while(off > 0){  
  20.     rec.off = off;  
  21.     if(!tchdbreadrec(hdb, &rec, rbuf)) return false//从数据文件读取指定偏移的记录,即对应hash值的第一个记录  
  22.     // 读取的方式为:首先看是否可以经过mmap映射的内存读,若可以则直接读,否则调用pread读取  
  23.     if(hash > rec.hash){ // 从这里开始比较要插入的值和刚刚读取的记录,比较它们的二级hash值  
  24.       off = rec.left;  
  25.       entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)); // 调整entoff  
  26.     } else if(hash < rec.hash){  
  27.       off = rec.right;  
  28.       entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)) +  
  29.         (hdb->ba64 ? sizeof(uint64_t) : sizeof(uint32_t));  
  30.     } else { // hash值相等,接着比较记录的key  
  31.       if(!rec.kbuf && !tchdbreadrecbody(hdb, &rec)) return false;  
  32.       int kcmp = tcreckeycmp(kbuf, ksiz, rec.kbuf, rec.ksiz); // 比较记录的key值,看是否与已存记录相等  
  33.       if(kcmp > 0){  
  34.         off = rec.left;  
  35.         TCFREE(rec.bbuf);  
  36.         rec.kbuf = NULL;  
  37.         rec.bbuf = NULL;  
  38.         entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)); // 调整entoff  
  39.       } else if(kcmp < 0){  
  40.         off = rec.right;  
  41.         TCFREE(rec.bbuf);  
  42.         rec.kbuf = NULL;  
  43.         rec.bbuf = NULL;  
  44.         entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)) +  
  45.           (hdb->ba64 ? sizeof(uint64_t) : sizeof(uint32_t));  
  46.       } else { // key值相等,在这里说明有相同记录存在,下面根据传入的模式进行相应处理  
  47.         bool rv;  
  48.         int nvsiz;  
  49.         char *nvbuf;  
  50.         HDBPDPROCOP *procptr;  
  51.         switch(dmode){  
  52.           case HDBPDKEEP:  // 保持原记录不变  
  53.             tchdbsetecode(hdb, TCEKEEP, __FILE__, __LINE__, __func__);  
  54.             TCFREE(rec.bbuf);  
  55.             return false;  
  56.           case HDBPDCAT: // 附加到原记录后面  
  57.             if(vsiz < 1){  
  58.               TCFREE(rec.bbuf);  
  59.               return true;  
  60.             }  
  61.             if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)){  
  62.               TCFREE(rec.bbuf);  
  63.               return false;  
  64.             }  
  65.             nvsiz = rec.vsiz + vsiz;  
  66.             if(rec.bbuf){  
  67.               TCREALLOC(rec.bbuf, rec.bbuf, rec.ksiz + nvsiz);  
  68.               memcpy(rec.bbuf + rec.ksiz + rec.vsiz, vbuf, vsiz);  
  69.               rec.kbuf = rec.bbuf;  
  70.               rec.vbuf = rec.kbuf + rec.ksiz;  
  71.               rec.vsiz = nvsiz;  
  72.             } else {  
  73.               TCMALLOC(rec.bbuf, nvsiz + 1);  
  74.               memcpy(rec.bbuf, rec.vbuf, rec.vsiz);  
  75.               memcpy(rec.bbuf + rec.vsiz, vbuf, vsiz);  
  76.               rec.vbuf = rec.bbuf;  
  77.               rec.vsiz = nvsiz;  
  78.             }  
  79.             rv = tchdbwriterec(hdb, &rec, bidx, entoff);  
  80.             TCFREE(rec.bbuf);  
  81.             return rv;  
  82.           case HDBPDADDINT: // 向原记录增加一个整数值,原记录也是整数  
  83.             if(rec.vsiz != sizeof(int)){  
  84.               tchdbsetecode(hdb, TCEKEEP, __FILE__, __LINE__, __func__);  
  85.               TCFREE(rec.bbuf);  
  86.               return false;  
  87.             }  
  88.             if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)){  
  89.               TCFREE(rec.bbuf);  
  90.               return false;  
  91.             }  
  92.             int lnum;  
  93.             memcpy(&lnum, rec.vbuf, sizeof(lnum));  
  94.             if(*(int *)vbuf == 0){  
  95.               TCFREE(rec.bbuf);  
  96.               *(int *)vbuf = lnum;  
  97.               return true;  
  98.             }  
  99.             lnum += *(int *)vbuf;  
  100.             rec.vbuf = (char *)&lnum;  
  101.             *(int *)vbuf = lnum;  
  102.             rv = tchdbwriterec(hdb, &rec, bidx, entoff);  
  103.             TCFREE(rec.bbuf);  
  104.             return rv;  
  105.           case HDBPDADDDBL:  // 向原记录增加一个浮点数值  
  106.             if(rec.vsiz != sizeof(double)){  
  107.               tchdbsetecode(hdb, TCEKEEP, __FILE__, __LINE__, __func__);  
  108.               TCFREE(rec.bbuf);  
  109.               return false;  
  110.             }  
  111.             if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)){  
  112.               TCFREE(rec.bbuf);  
  113.               return false;  
  114.             }  
  115.             double dnum;  
  116.             memcpy(&dnum, rec.vbuf, sizeof(dnum));  
  117.             if(*(double *)vbuf == 0.0){  
  118.               TCFREE(rec.bbuf);  
  119.               *(double *)vbuf = dnum;  
  120.               return true;  
  121.             }  
  122.             dnum += *(double *)vbuf;  
  123.             rec.vbuf = (char *)&dnum;  
  124.             *(double *)vbuf = dnum;  
  125.             rv = tchdbwriterec(hdb, &rec, bidx, entoff);  
  126.             TCFREE(rec.bbuf);  
  127.             return rv;  
  128.           case HDBPDPROC: // 调用回调函数处理原记录  
  129.             if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)){  
  130.               TCFREE(rec.bbuf);  
  131.               return false;  
  132.             }  
  133.             procptr = *(HDBPDPROCOP **)((char *)kbuf - sizeof(procptr));  
  134.             nvbuf = procptr->proc(rec.vbuf, rec.vsiz, &nvsiz, procptr->op);  
  135.             TCFREE(rec.bbuf);  
  136.             if(nvbuf == (void *)-1){  
  137.               return tchdbremoverec(hdb, &rec, rbuf, bidx, entoff);  
  138.             } else if(nvbuf){  
  139.               rec.kbuf = kbuf;  
  140.               rec.ksiz = ksiz;  
  141.               rec.vbuf = nvbuf;  
  142.               rec.vsiz = nvsiz;  
  143.               rv = tchdbwriterec(hdb, &rec, bidx, entoff);  
  144.               TCFREE(nvbuf);  
  145.               return rv;  
  146.             }  
  147.             tchdbsetecode(hdb, TCEKEEP, __FILE__, __LINE__, __func__);  
  148.             return false;  
  149.           default:  
  150.             break;  
  151.         }  
  152.         TCFREE(rec.bbuf);  
  153.         rec.ksiz = ksiz;  
  154.         rec.vsiz = vsiz;  
  155.         rec.kbuf = kbuf;  
  156.         rec.vbuf = vbuf;  
  157.         return tchdbwriterec(hdb, &rec, bidx, entoff);  
  158.       }  
  159.     }  
  160.   }  
  161.   // 执行到这里,说明应写入新记录  
  162.   if(!vbuf){  
  163.     tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);  
  164.     return false;  
  165.   }  
  166.   if(!HDBLOCKDB(hdb)) return false;  
  167.   // 构造文件中的记录格式头部,格式为: magic number(1bytes) + hash value(1bytes)  + left(4bytes or 8bytes) +   
  168.   // right chain(4bytes or 8bytes) + padding size(2bytes)  
  169.   rec.rsiz = hdb->ba64 ? sizeof(uint8_t) * 2 + sizeof(uint64_t) * 2 + sizeof(uint16_t) :  
  170.     sizeof(uint8_t) * 2 + sizeof(uint32_t) * 2 + sizeof(uint16_t);  
  171.   // 键值的存储变量(区域)是变长的,我们在这里计算出其存储区长度,每个字节只用7位,第8位作为符号位     
  172.   if(ksiz < (1U << 7)){  
  173.     rec.rsiz += 1;  
  174.   } else if(ksiz < (1U << 14)){  
  175.     rec.rsiz += 2;  
  176.   } else if(ksiz < (1U << 21)){  
  177.     rec.rsiz += 3;  
  178.   } else if(ksiz < (1U << 28)){  
  179.     rec.rsiz += 4;  
  180.   } else {  
  181.     rec.rsiz += 5;  
  182.   }  
  183.   // value值长度和键值一样,同上  
  184.   if(vsiz < (1U << 7)){  
  185.     rec.rsiz += 1;  
  186.   } else if(vsiz < (1U << 14)){  
  187.     rec.rsiz += 2;  
  188.   } else if(vsiz < (1U << 21)){  
  189.     rec.rsiz += 3;  
  190.   } else if(vsiz < (1U << 28)){  
  191.     rec.rsiz += 4;  
  192.   } else {  
  193.     rec.rsiz += 5;  
  194.   }  
  195.   if(!tchdbfbpsearch(hdb, &rec)){ // 首先看能否从空闲快数组找到合适的记录块,若不能,则标记为应添加到文件末尾  
  196.     HDBUNLOCKDB(hdb);  
  197.     return false;  
  198.   }  
  199.   // 下面几条语句赋予的值将被存放在数据文件的记录中,它们在记录中的顺序和这里赋值的顺序恰好一致,呵呵,不同  
  200.   // 的是,数据文件记录中在这几个值后面还有一个对齐填充区,这样下一个记录就能从对齐字节开始存放了。  
  201.   rec.hash = hash;  
  202.   rec.left = 0;  
  203.   rec.right = 0;  
  204.   rec.ksiz = ksiz;  
  205.   rec.vsiz = vsiz;  
  206.   rec.psiz = 0;  
  207.   rec.kbuf = kbuf;  
  208.   rec.vbuf = vbuf;  
  209.   if(!tchdbwriterec(hdb, &rec, bidx, entoff)){ // 向数据文件写入记录  
  210.     HDBUNLOCKDB(hdb);  
  211.     return false;  
  212.   }  
  213.   // 更新数据文件的统计信息,这里通过向mmap映射的内存区拷贝数据实现,因为我们至少会mmap数据文件控制信息到内存  
  214.   hdb->rnum++;   
  215.   uint64_t llnum = hdb->rnum;  
  216.   llnum = TCHTOILL(llnum);  
  217.   memcpy(hdb->map + HDBRNUMOFF, &llnum, sizeof(llnum));  
  218.   HDBUNLOCKDB(hdb);  
  219.   return true;  
  220. }  
    

    在上面查找冲突树的过程中,由于是先比较二级hash值,找到相等的值,再进行key值的比较,最终决定写入记录的位置,因此对于具有相同二级hash值的记录而言,它们可能会由于key的不同而导致与其它具有不同二级hash值的记录相互混杂,由这里我们可以知道,插入过程构造出来的冲突树是非常杂乱无章的,没有规律可言,这也许能进一步促进冲突数区域平衡吧。


Tokyo Cabinet TCHDB源码阅读——tchdbnew、tchdbopen及相关函数代码

  在此说明一下:我分析的TC版本是1.4.43,我的阅读流程主要是遵循对TC数据文件的标准操作流程进行分析,即打开数据文件->存放record->获取record->关闭数据文件,期间会涉及到一些相关函数,我会逐个分析。

另外,按我粗浅的理解,我觉得TC不能称为一个完整意义上的数据库,它仅仅是一个存储引擎而已,它的所有操作最终都是针对系统中的一个普通文件进行的,因此以后的分析中,我把TC操作的对象统统称为数据文件对象,而不是数据库对象,它打开的文件称为数据文件。

 

一、TCHDB *tchdbnew(void)

     这个函数实现的功能是:分配一个TCHDB类型的数据文件对象,我们以后的操作都是基于这个对象的,它的功能有点类似于我们打开文件后获得的那个文件描述符,不过TCHDB比文件描述符要复杂的多,下面是代码和注释:

TCHDB *tchdbnew(void){
  TCHDB *hdb;
  TCMALLOC(hdb, sizeof(*hdb));  // 动态分配一个TCHDB对象
  tchdbclear(hdb); // 初始化TCHDB对象
  return hdb;
}

 

下面是tchdbclear对应的代码和注释:

[cpp]  view plain copy
  1. /* Clear all members. 
  2.    `hdb' specifies the hash database object. */  
  3. static void tchdbclear(TCHDB *hdb){  
  4.   assert(hdb);  
  5.   hdb->mmtx = NULL;  
  6.   hdb->rmtxs = NULL;  
  7.   hdb->dmtx = NULL;  
  8.   hdb->wmtx = NULL;  
  9.   hdb->eckey = NULL;  
  10.   hdb->rpath = NULL;  
  11.   hdb->type = TCDBTHASH; // 类型为hash结构的数据文件  
  12.   hdb->flags = 0;  
  13.   hdb->bnum = HDBDEFBNUM; // 默认hash桶大小131071  
  14.   hdb->apow = HDBDEFAPOW;  
  15.   hdb->fpow = HDBDEFFPOW;  
  16.   hdb->opts = 0;  
  17.   hdb->path = NULL;  
  18.   hdb->fd = -1;  
  19.   hdb->omode = 0;  
  20.   hdb->rnum = 0;  
  21.   hdb->fsiz = 0;  
  22.   hdb->frec = 0;  
  23.   hdb->dfcur = 0;  
  24.   hdb->iter = 0;  
  25.   hdb->map = NULL;  
  26.   hdb->msiz = 0;  
  27.   hdb->xmsiz = HDBDEFXMSIZ; // 默认mmap大小64M  
  28.   hdb->xfsiz = 0;  
  29.   hdb->ba32 = NULL;  
  30.   hdb->ba64 = NULL;  
  31.   hdb->align = 0;  
  32.   hdb->runit = 0;  
  33.   hdb->zmode = false;  
  34.   hdb->fbpmax = 0;  
  35.   hdb->fbpool = NULL;  
  36.   hdb->fbpnum = 0;  
  37.   hdb->fbpmis = 0;  
  38.   hdb->async = false;  
  39.   hdb->drpool = NULL;  
  40.   hdb->drpdef = NULL;  
  41.   hdb->drpoff = 0;  
  42.   hdb->recc = NULL;  
  43.   hdb->rcnum = 0;  
  44.   hdb->enc = NULL;  
  45.   hdb->encop = NULL;  
  46.   hdb->dec = NULL;  
  47.   hdb->decop = NULL;  
  48.   hdb->ecode = TCESUCCESS;  
  49.   hdb->fatal = false;  
  50.   hdb->inode = 0;  
  51.   hdb->mtime = 0;  
  52.   hdb->dfunit = 0;  
  53.   hdb->dfcnt = 0;  
  54.   hdb->tran = false;  
  55.   hdb->walfd = -1;  
  56.   hdb->walend = 0;  
  57.   hdb->dbgfd = -1;  
  58.   hdb->cnt_writerec = -1;  
  59.   hdb->cnt_reuserec = -1;  
  60.   hdb->cnt_moverec = -1;  
  61.   hdb->cnt_readrec = -1;  
  62.   hdb->cnt_searchfbp = -1;  
  63.   hdb->cnt_insertfbp = -1;  
  64.   hdb->cnt_splicefbp = -1;  
  65.   hdb->cnt_dividefbp = -1;  
  66.   hdb->cnt_mergefbp = -1;  
  67.   hdb->cnt_reducefbp = -1;  
  68.   hdb->cnt_appenddrp = -1;  
  69.   hdb->cnt_deferdrp = -1;  
  70.   hdb->cnt_flushdrp = -1;  
  71.   hdb->cnt_adjrecc = -1;  
  72.   hdb->cnt_defrag = -1;  
  73.   hdb->cnt_shiftrec = -1;  
  74.   hdb->cnt_trunc = -1;  
  75.   TCDODEBUG(hdb->cnt_writerec = 0);  
  76.   TCDODEBUG(hdb->cnt_reuserec = 0);  
  77.   TCDODEBUG(hdb->cnt_moverec = 0);  
  78.   TCDODEBUG(hdb->cnt_readrec = 0);  
  79.   TCDODEBUG(hdb->cnt_searchfbp = 0);  
  80.   TCDODEBUG(hdb->cnt_insertfbp = 0);  
  81.   TCDODEBUG(hdb->cnt_splicefbp = 0);  
  82.   TCDODEBUG(hdb->cnt_dividefbp = 0);  
  83.   TCDODEBUG(hdb->cnt_mergefbp = 0);  
  84.   TCDODEBUG(hdb->cnt_reducefbp = 0);  
  85.   TCDODEBUG(hdb->cnt_appenddrp = 0);  
  86.   TCDODEBUG(hdb->cnt_deferdrp = 0);  
  87.   TCDODEBUG(hdb->cnt_flushdrp = 0);  
  88.   TCDODEBUG(hdb->cnt_adjrecc = 0);  
  89.   TCDODEBUG(hdb->cnt_defrag = 0);  
  90.   TCDODEBUG(hdb->cnt_shiftrec = 0);  
  91.   TCDODEBUG(hdb->cnt_trunc = 0);  
  92. }  

 

二、tchdbopen函数

 该函数打开指定的数据文件,并将打开的数据文件信息添加到TCHDB结构体中,打开过程中会检查数据文件是否为空等操作,看代码:

[cpp]  view plain copy
  1. /* Open a database file and connect a hash database object. */  
  2. bool tchdbopen(TCHDB *hdb, const char *path, int omode){  
  3.   assert(hdb && path);  
  4.   if(!HDBLOCKMETHOD(hdb, true)) return false// 获得调用函数锁  
  5.   if(hdb->fd >= 0){ // 若数据文件已打开,则返回出错  
  6.     tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__); // 设置错误码  
  7.     HDBUNLOCKMETHOD(hdb);  
  8.     return false;  
  9.   }  
  10.   char *rpath = tcrealpath(path); // 取得数据文件的标准化路径  
  11.   if(!rpath){  
  12.     int ecode = TCEOPEN;  
  13.     switch(errno){  
  14.       case EACCES: ecode = TCENOPERM; break;  
  15.       case ENOENT: ecode = TCENOFILE; break;  
  16.       case ENOTDIR: ecode = TCENOFILE; break;  
  17.     }  
  18.     tchdbsetecode(hdb, ecode, __FILE__, __LINE__, __func__);  
  19.     HDBUNLOCKMETHOD(hdb);  
  20.     return false;  
  21.   }  
  22.   if(!tcpathlock(rpath)){ // 锁定文件路径, 以防止在操作过程中文件消失  
  23.     tchdbsetecode(hdb, TCETHREAD, __FILE__, __LINE__, __func__);  
  24.     TCFREE(rpath);  
  25.     HDBUNLOCKMETHOD(hdb);  
  26.     return false;  
  27.   }  
  28.   bool rv = tchdbopenimpl(hdb, path, omode); // 具体打开操作  
  29.   if(rv){ // 打开数据文件成功,保存数据文件的标准化路径  
  30.     hdb->rpath = rpath;  
  31.   } else {  
  32.     tcpathunlock(rpath);  
  33.     TCFREE(rpath);  
  34.   }  
  35.   HDBUNLOCKMETHOD(hdb);  
  36.   return rv; // 返回打开是否成功  
  37. }  

 

函数tchdbopen仅仅做简单的一些检查转换操作,具体打开操作在函数tchdbopenimpl中,我们来看看代码:

 

[cpp]  view plain copy
  1. /* Open a database file and connect a hash database object. 
  2.    `hdb' specifies the hash database object. 
  3.    `path' specifies the path of the database file. 
  4.    `omode' specifies the connection mode. 
  5.    If successful, the return value is true, else, it is false. */  
  6. static bool tchdbopenimpl(TCHDB *hdb, const char *path, int omode){  
  7.   assert(hdb && path);  
  8.   int mode = O_RDONLY;  
  9.   if(omode & HDBOWRITER){ // 取得数据文件打开模式  
  10.     mode = O_RDWR;  
  11.     if(omode & HDBOCREAT) mode |= O_CREAT;  
  12.   }  
  13.   int fd = open(path, mode, HDBFILEMODE); // 调用库函数open打开数据文件,这里可以看到,文件仅仅是普通文件而已。  
  14.   if(fd < 0){  
  15.     int ecode = TCEOPEN;  
  16.     switch(errno){  
  17.       case EACCES: ecode = TCENOPERM; break;  
  18.       case ENOENT: ecode = TCENOFILE; break;  
  19.       case ENOTDIR: ecode = TCENOFILE; break;  
  20.     }  
  21.     tchdbsetecode(hdb, ecode, __FILE__, __LINE__, __func__);  
  22.     return false;  
  23.   }  
  24.   if(!(omode & HDBONOLCK)){ // 检查是否需要锁定打开的数据文件,这里用的是advisory lock,通过fcntl实现  
  25.     if(!tclock(fd, omode & HDBOWRITER, omode & HDBOLCKNB)){  
  26.       tchdbsetecode(hdb, TCELOCK, __FILE__, __LINE__, __func__);  
  27.       close(fd);  
  28.       return false;  
  29.     }  
  30.   }  
  31.   if((omode & HDBOWRITER) && (omode & HDBOTRUNC)){ // 写操作,是否需要截短文件?  
  32.     if(ftruncate(fd, 0) == -1){  
  33.       tchdbsetecode(hdb, TCETRUNC, __FILE__, __LINE__, __func__);  
  34.       close(fd);  
  35.       return false;  
  36.     }  
  37.     if(!tchdbwalremove(hdb, path)){  
  38.       close(fd);  
  39.       return false;  
  40.     }  
  41.   }  
  42.   struct stat sbuf;  
  43.   if(fstat(fd, &sbuf) == -1 || !S_ISREG(sbuf.st_mode)){ // 获得文件属性信息,判断文件是否为普通文件  
  44.     tchdbsetecode(hdb, TCESTAT, __FILE__, __LINE__, __func__);  
  45.     close(fd);  
  46.     return false;  
  47.   }  
  48.   char hbuf[HDBHEADSIZ];  
  49.   if((omode & HDBOWRITER) && sbuf.st_size < 1){ // 写模式下,是否为空文件?  
  50.   // 写模式下为空文件,则这里开始构造默认情况下的数据文件头信息,从这里我们也可以推测出数   
  51.   // 据文件头在磁盘文件中的存放格式,它的大小为256字节,参见HDBHEADSIZ。  
  52.     hdb->flags = 0;  // 默认flags为0  
  53.     hdb->rnum = 0;    // 当前记录为0  
  54.     uint32_t fbpmax = 1 << hdb->fpow; // 空闲块池默认大小  
  55.     uint32_t fbpsiz = HDBFBPBSIZ + fbpmax * HDBFBPESIZ; // 默认空闲块池大小  
  56.     int besiz = (hdb->opts & HDBTLARGE) ? sizeof(int64_t) : sizeof(int32_t); // hash桶数组中每个元素大小  
  57.     hdb->align = 1 << hdb->apow; // 默认对齐字节  
  58.     hdb->fsiz = HDBHEADSIZ + besiz * hdb->bnum + fbpsiz; // 不包含填充区的文件大小  
  59.     hdb->fsiz += tchdbpadsize(hdb, hdb->fsiz); // 包含填充区的文件大小  
  60.     hdb->frec = hdb->fsiz; // 数据文件中第一个记录在文件中的偏移  
  61.     tchdbdumpmeta(hdb, hbuf); // 将数据文件头信息拷贝到hbuf  
  62.     bool err = false;  
  63.     if(!tcwrite(fd, hbuf, HDBHEADSIZ)) err = true// 将数据文件头信息写入数据文件  
  64.     char pbuf[HDBIOBUFSIZ];  
  65.     memset(pbuf, 0, HDBIOBUFSIZ);  
  66.     uint64_t psiz = hdb->fsiz - HDBHEADSIZ;  
  67.     while(psiz > 0){ // 这个循环将数据文件头部的填充区置位0,有什么用?可能是后面写入新记录时会读到这个区域...  
  68.       if(psiz > HDBIOBUFSIZ){  
  69.         if(!tcwrite(fd, pbuf, HDBIOBUFSIZ)) err = true;  
  70.         psiz -= HDBIOBUFSIZ;  
  71.       } else {  
  72.         if(!tcwrite(fd, pbuf, psiz)) err = true;  
  73.         psiz = 0;  
  74.       }  
  75.     }  
  76.     if(err){  
  77.       tchdbsetecode(hdb, TCEWRITE, __FILE__, __LINE__, __func__);  
  78.       close(fd);  
  79.       return false;  
  80.     }  
  81.     sbuf.st_size = hdb->fsiz; // 改写文件属性信息,改写文件大小  
  82.   }  
  83.   if(lseek(fd, 0, SEEK_SET) == -1){ // 重置文件指针到数据文件头部  
  84.     tchdbsetecode(hdb, TCESEEK, __FILE__, __LINE__, __func__);  
  85.     close(fd);  
  86.     return false;  
  87.   }  
  88.   if(!tcread(fd, hbuf, HDBHEADSIZ)){ // 读出文件头信息,保证hbuf中含有文件头信息  
  89.     tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);  
  90.     close(fd);  
  91.     return false;  
  92.   }  
  93.   int type = hdb->type;  
  94.   tchdbloadmeta(hdb, hbuf); // 将文件头信息copy到TCHDB结构体中,即初始化TCHDB结构体某些成员  
  95.     
  96.   // 若文件被标识为已打开,很可能前面针对文件的操作在事务中被中断了,这里将数据文件从wal文件中恢复回来,  
  97.   // 相当于取消了前面那个事务,目前我们只要知道wal文件用于事务即可,这个函数后面我会补充分析。  
  98.   if((hdb->flags & HDBFOPEN) && tchdbwalrestore(hdb, path)){  
  99.     if(lseek(fd, 0, SEEK_SET) == -1){   // 数据文件已恢复,下面需要重新读取头部信息                       
  100.       tchdbsetecode(hdb, TCESEEK, __FILE__, __LINE__, __func__);   
  101.       close(fd);  
  102.       return false;  
  103.     }  
  104.     if(!tcread(fd, hbuf, HDBHEADSIZ)){ // 重新读出出数据文件头信息  
  105.       tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);  
  106.       close(fd);  
  107.       return false;  
  108.     }  
  109.     tchdbloadmeta(hdb, hbuf);// 重新导出出数据文件头信息  
  110.     if(!tchdbwalremove(hdb, path)){  
  111.       close(fd);  
  112.       return false;  
  113.     }  
  114.   }  
  115.   int besiz = (hdb->opts & HDBTLARGE) ? sizeof(int64_t) : sizeof(int32_t); // hash桶大小  
  116.   size_t msiz = HDBHEADSIZ + hdb->bnum * besiz; // 算出数据文件最小需要mmap的大小,即数据文件头大小加hash同数组大小  
  117.   if(!(omode & HDBONOLCK)){  
  118.     if(memcmp(hbuf, HDBMAGICDATA, strlen(HDBMAGICDATA)) || hdb->type != type ||  
  119.        hdb->frec < msiz + HDBFBPBSIZ || hdb->frec > hdb->fsiz || sbuf.st_size < hdb->fsiz){  
  120.       tchdbsetecode(hdb, TCEMETA, __FILE__, __LINE__, __func__);  
  121.       close(fd);  
  122.       return false;  
  123.     }  
  124.   }  
  125.   if(((hdb->opts & HDBTDEFLATE) && !_tc_deflate) ||  
  126.      ((hdb->opts & HDBTBZIP) && !_tc_bzcompress) || ((hdb->opts & HDBTEXCODEC) && !hdb->enc)){  
  127.     tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);  
  128.     close(fd);  
  129.     return false;  
  130.   }  
  131.   size_t xmsiz = (hdb->xmsiz > msiz) ? hdb->xmsiz : msiz; // 数据文件需要mmap的大小,至少为msize  
  132.   if(!(omode & HDBOWRITER) && xmsiz > hdb->fsiz) xmsiz = hdb->fsiz; // mmap的大小不超过文件大小  
  133.   void *map = mmap(0, xmsiz, PROT_READ | ((omode & HDBOWRITER) ? PROT_WRITE : 0),  
  134.                    MAP_SHARED, fd, 0); // 映射指定大小数据文件  
  135.   if(map == MAP_FAILED){  
  136.     tchdbsetecode(hdb, TCEMMAP, __FILE__, __LINE__, __func__);  
  137.     close(fd);  
  138.     return false;  
  139.   }  
  140.   hdb->fbpmax = 1 << hdb->fpow; // 空闲块池区域最大数量  
  141.   if(omode & HDBOWRITER){ // 写模式,分配空闲块池  
  142.     TCMALLOC(hdb->fbpool, hdb->fbpmax * HDBFBPALWRAT * sizeof(HDBFB));  
  143.   } else {  
  144.     hdb->fbpool = NULL; // 非写模式,不分配空闲块池,节约内存  
  145.   }  
  146.   hdb->fbpnum = 0; // 其它值的初始化  
  147.   hdb->fbpmis = 0;  
  148.   hdb->async = false;  
  149.   hdb->drpool = NULL;  
  150.   hdb->drpdef = NULL;  
  151.   hdb->drpoff = 0;  
  152.   hdb->recc = (hdb->rcnum > 0) ? tcmdbnew2(hdb->rcnum * 2 + 1) : NULL; // record的缓冲区大小计算  
  153.   hdb->path = tcstrdup(path); // 复制一份数据文件raw路径,没有经过标准化处理的路径。  
  154.   hdb->fd = fd; // 从这里开始复制一些值到TCHDB结构体中  
  155.   hdb->omode = omode;  
  156.   hdb->dfcur = hdb->frec;  
  157.   hdb->iter = 0;  
  158.   hdb->map = map;  
  159.   hdb->msiz = msiz;  
  160.   hdb->xfsiz = 0;  
  161.   if(hdb->opts & HDBTLARGE){ // 判断使用32位还是64为的hash桶  
  162.     hdb->ba32 = NULL;  
  163.     hdb->ba64 = (uint64_t *)((char *)map + HDBHEADSIZ);  
  164.   } else {  
  165.     hdb->ba32 = (uint32_t *)((char *)map + HDBHEADSIZ);  
  166.     hdb->ba64 = NULL;  
  167.   }  
  168.   hdb->align = 1 << hdb->apow;  
  169.   hdb->runit = tclmin(tclmax(hdb->align, HDBMINRUNIT), HDBIOBUFSIZ);  
  170.   hdb->zmode = (hdb->opts & HDBTDEFLATE) || (hdb->opts & HDBTBZIP) ||  
  171.     (hdb->opts & HDBTTCBS) || (hdb->opts & HDBTEXCODEC);  
  172.   hdb->ecode = TCESUCCESS;  
  173.   hdb->fatal = false;  
  174.   hdb->inode = (uint64_t)sbuf.st_ino; // 数据文件的inode号  
  175.   hdb->mtime = sbuf.st_mtime; // 数据文件修改时间  
  176.   hdb->dfcnt = 0;  
  177.   hdb->tran = false;  
  178.   hdb->walfd = -1;  
  179.   hdb->walend = 0;  
  180.   if(hdb->omode & HDBOWRITER){ // 若是写模式,并且当前数据文件不处于打开状态,则从数据文件中读取fbp信息,在内存中建立fbp区域  
  181.     bool err = false;  
  182.     if(!(hdb->flags & HDBFOPEN) && !tchdbloadfbp(hdb)) err = true;  
  183.     memset(hbuf, 0, 2);  
  184.     if(!tchdbseekwrite(hdb, hdb->msiz, hbuf, 2)) err = true;  
  185.     if(err){  
  186.       TCFREE(hdb->path);  
  187.       TCFREE(hdb->fbpool);  
  188.       munmap(hdb->map, xmsiz);  
  189.       close(fd);  
  190.       hdb->fd = -1;  
  191.       return false;  
  192.     }  
  193.     tchdbsetflag(hdb, HDBFOPEN, true); // 设置数据文件已被打开标志  
  194.   }  
  195.   return true// 打开成功  
  196. }  

 

这里补充下上面函数中调用的tchdbwalrestore函数,wal文件的格式为:更新前数据文件大小(8字节) + 一条或多条更新记录,其中,每条更新记录格式如下:

wal文件+

 

[cpp]  view plain copy
  1. /* Restore the database from the write ahead logging file. 
  2.    `hdb' specifies the hash database object. 
  3.    `path' specifies the path of the database file. 
  4.    If successful, the return value is true, else, it is false. */  
  5. static int tchdbwalrestore(TCHDB *hdb, const char *path){  
  6.   assert(hdb && path);  
  7.   char *tpath = tcsprintf("%s%c%s", path, MYEXTCHR, HDBWALSUFFIX); // 构造数据文件对应的wal文件名  
  8.   int walfd = open(tpath, O_RDONLY, HDBFILEMODE); // 打开wal文件  
  9.   TCFREE(tpath);  
  10.   if(walfd < 0) return false;  
  11.   bool err = false;  
  12.   uint64_t walsiz = 0;  
  13.   struct stat sbuf;  
  14.   if(fstat(walfd, &sbuf) == 0){ // 获得wal文件属性信息  
  15.     walsiz = sbuf.st_size;  
  16.   } else {  
  17.     tchdbsetecode(hdb, TCESTAT, __FILE__, __LINE__, __func__);  
  18.     err = true;  
  19.   }  
  20.   if(walsiz >= sizeof(walsiz) + HDBHEADSIZ){ // 粗略判断wal文件中是否有更新操作记录,我的理解是:有更新操作必然会导致更新数据文件头信息,因此这里的判断可以根据这一点来做一点优化。  
  21.     int dbfd = hdb->fd;  
  22.     int tfd = -1;  
  23.     if(!(hdb->omode & HDBOWRITER)){ // 若不是写模式,则需要打开数据文件,以进行后面的操作  
  24.       tfd = open(path, O_WRONLY, HDBFILEMODE);  
  25.       if(tfd >= 0){  
  26.         dbfd = tfd;  
  27.       } else {  
  28.         int ecode = TCEOPEN;  
  29.         switch(errno){  
  30.           case EACCES: ecode = TCENOPERM; break;  
  31.           case ENOENT: ecode = TCENOFILE; break;  
  32.           case ENOTDIR: ecode = TCENOFILE; break;  
  33.         }  
  34.         tchdbsetecode(hdb, ecode, __FILE__, __LINE__, __func__);  
  35.         err = true;  
  36.       }  
  37.     }  
  38.     uint64_t fsiz = 0;  
  39.     if(tcread(walfd, &fsiz, sizeof(fsiz))){ // wal文件头8字节包含了事务开始前数据文件大小  
  40.       fsiz = TCITOHLL(fsiz);  
  41.     } else {  
  42.       tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);  
  43.       err = true;  
  44.     }  
  45.     TCLIST *list = tclistnew();  
  46.     uint64_t waloff = sizeof(fsiz);  
  47.     char stack[HDBIOBUFSIZ];  
  48.     while(waloff < walsiz){ // 循环读取wal文件中的更新记录,将读到的记录都保存在list中  
  49.       uint64_t off; // 存放wal文件中记录的更新区域offset  
  50.       uint32_t size; // 存放wal文件中记录的更新区域大小  
  51.       if(!tcread(walfd, stack, sizeof(off) + sizeof(size))){ // 从wal文件中读取off和size到stack中  
  52.         tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);  
  53.         err = true;  
  54.         break;  
  55.       }  
  56.       // 拷贝读到的值到off和size变量并转换成实际值  
  57.       memcpy(&off, stack, sizeof(off));  
  58.       off = TCITOHLL(off);  
  59.       memcpy(&size, stack + sizeof(off), sizeof(size));  
  60.       size = TCITOHL(size);  
  61.       char *buf; // buf用来存放更新区域offset和更新区域数据(更新前的数据),这里检查能否复用stack,否则动态分配空间  
  62.       if(sizeof(off) + size <= HDBIOBUFSIZ){  
  63.         buf = stack;  
  64.       } else {  
  65.         TCMALLOC(buf, sizeof(off) + size);  
  66.       }  
  67.       *(uint64_t *)buf = off; // 存放offset到buf中  
  68.       if(!tcread(walfd, buf + sizeof(off), size)){ //读取更新前数据到buf中  
  69.         tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);  
  70.         err = true;  
  71.         if(buf != stack) TCFREE(buf);  
  72.         break;  
  73.       }  
  74.       TCLISTPUSH(list, buf, sizeof(off) + size); // 存放buf到链表list中,这个链表会自动增长  
  75.       if(buf != stack) TCFREE(buf);  
  76.       waloff += sizeof(off) + sizeof(size) + size; // 移动waloff指针,循环检查是否还有其它的更改,若有,则继续读取  
  77.     }  
  78.     size_t xmsiz = 0;  
  79.     if(hdb->fd >= 0 && hdb->map) xmsiz = (hdb->xmsiz > hdb->msiz) ? hdb->xmsiz : hdb->msiz;  
  80.     for(int i = TCLISTNUM(list) - 1; i >= 0; i--){  
  81.       const char *rec;  
  82.       int size;  
  83.       TCLISTVAL(rec, list, i, size);  
  84.       uint64_t off = *(uint64_t *)rec; // rec指向的内存包含信息: 区域大小(8字节) + 区域数据  
  85.       rec += sizeof(off); // 调整rec指向更新区域更新前的数据  
  86.       size -= sizeof(off); //  size为区域数据大小  
  87.       if(lseek(dbfd, off, SEEK_SET) == -1){ // 移动数据文件指针,向数据文件写入更新前数据  
  88.         tchdbsetecode(hdb, TCESEEK, __FILE__, __LINE__, __func__);  
  89.         err = true;  
  90.         break;  
  91.       }  
  92.       if(!tcwrite(dbfd, rec, size)){  
  93.         tchdbsetecode(hdb, TCEWRITE, __FILE__, __LINE__, __func__);  
  94.         err = true;  
  95.         break;  
  96.       }  
  97.       if(!TCUBCACHE && off < xmsiz){  
  98.         size = (size <= xmsiz - off) ? size : xmsiz - off;  
  99.         memcpy(hdb->map + off, rec, size);  
  100.       }  
  101.     }  
  102.     tclistdel(list); // 释放链表空间  
  103.     if(ftruncate(dbfd, fsiz) == -1){ // 调整数据文件大小,去掉尾部多余数据  
  104.       tchdbsetecode(hdb, TCETRUNC, __FILE__, __LINE__, __func__);  
  105.       err = true;  
  106.     }  
  107.     if((hdb->omode & HDBOTSYNC) && fsync(dbfd) == -1){ // 若有可能,同步事务操作  
  108.       tchdbsetecode(hdb, TCESYNC, __FILE__, __LINE__, __func__);  
  109.       err = true;  
  110.     }  
  111.     if(tfd >= 0 && close(tfd) == -1){ // 若在这个函数中打开了数据文件,则关闭数据文件文件描述符  
  112.       tchdbsetecode(hdb, TCECLOSE, __FILE__, __LINE__, __func__);  
  113.       err = true;  
  114.     }  
  115.   } else {  
  116.     err = true;  
  117.   }  
  118.   if(close(walfd) == -1){ // 关闭wal文件  
  119.     tchdbsetecode(hdb, TCECLOSE, __FILE__, __LINE__, __func__);  
  120.     err = true;  
  121.   }  
  122.   return !err; // 返回操作结果  
  123. }  

Tokyo Cabinet TCHDB源码阅读——tchdbput及相关函数代码注释

    tchdbput用于向数据文件写入一条记录(record),写入的模式有很多种,比如覆盖写(overwrite)、追加写(append)、加一个整数、加一个浮点数或者调用回调函数处理,然后把结果当作记录写入等等,这个函数在执行时是直接写入硬盘中的数据文件的,和它对应有一个异步写入的函数,名为tchdbputasync,此函数暂时把记录放于drp(delayed record pool)中,在合适的时候再写回硬盘,我后面会有相应函数分析。下面我贴出tchdbput函数的代码及注释,仅当成个人学习记录:

[cpp]  view plain copy
  1. /* Store a record into a hash database object. */  
  2. bool tchdbput(TCHDB *hdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){  
  3.   assert(hdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);  
  4.   if(!HDBLOCKMETHOD(hdb, false)) return false// 检查是否可获得函数锁  
  5.   uint8_t hash;  
  6.  // 根据key值计算出2个hash值bidx和hash,其中bidx用户索引hash桶数组,hash用于在冲突数中  
  7. //查找记录位置,另外冲突数也在很大程度上依靠hash来获得尽量的平衡  
  8.   uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);  
  9.   if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER)){ // 检查文件是否已打开或当前是否为写模式  
  10.     tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);  
  11.     HDBUNLOCKMETHOD(hdb);  
  12.     return false;  
  13.   }  
  14.   if(hdb->async && !tchdbflushdrp(hdb)){ // 若是处于异步操作模式,则刷新延迟写缓冲区  
  15.     HDBUNLOCKMETHOD(hdb);  
  16.     return false;  
  17.   }  
  18.   if(!HDBLOCKRECORD(hdb, bidx, true)){ // 检查是否可对记录进行操作  
  19.     HDBUNLOCKMETHOD(hdb);  
  20.     return false;  
  21.   }  
  22.   if(hdb->zmode){ // 是否压缩记录,若是,则选择相应压缩算法压缩记录后再存储记录  
  23.     char *zbuf;  
  24.     if(hdb->opts & HDBTDEFLATE){  
  25.       zbuf = _tc_deflate(vbuf, vsiz, &vsiz, _TCZMRAW);  
  26.     } else if(hdb->opts & HDBTBZIP){  
  27.       zbuf = _tc_bzcompress(vbuf, vsiz, &vsiz);  
  28.     } else if(hdb->opts & HDBTTCBS){  
  29.       zbuf = tcbsencode(vbuf, vsiz, &vsiz);  
  30.     } else {  
  31.       zbuf = hdb->enc(vbuf, vsiz, &vsiz, hdb->encop);  
  32.     }  
  33.     if(!zbuf){  
  34.       tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);  
  35.       HDBUNLOCKRECORD(hdb, bidx);  
  36.       HDBUNLOCKMETHOD(hdb);  
  37.       return false;  
  38.     }  
  39.     bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, zbuf, vsiz, HDBPDOVER);  
  40.     TCFREE(zbuf);  
  41.     HDBUNLOCKRECORD(hdb, bidx);  
  42.     HDBUNLOCKMETHOD(hdb);  
  43.     if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&  
  44.        !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;  
  45.     return rv;  
  46.   }  
  47.   bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, vbuf, vsiz, HDBPDOVER); // 调用tchdbputimpl写入记录  
  48.   HDBUNLOCKRECORD(hdb, bidx);  
  49.   HDBUNLOCKMETHOD(hdb);  
  50.   if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&  
  51.      !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;  
  52.   return rv;  
  53. }  
 

  上面的函数中,主要是通过调用tchdbputimpl来实现写入记录功能,该函数如下:

[cpp]  view plain copy
  1. /* Store a record. 
  2.    `hdb' specifies the hash database object. 
  3.    `kbuf' specifies the pointer to the region of the key. 
  4.    `ksiz' specifies the size of the region of the key. 
  5.    `bidx' specifies the index of the bucket array. 
  6.    `hash' specifies the hash value for the collision tree. 
  7.    `vbuf' specifies the pointer to the region of the value. 
  8.    `vsiz' specifies the size of the region of the value. 
  9.    `dmode' specifies behavior when the key overlaps. 
  10.    If successful, the return value is true, else, it is false. */  
  11. static bool tchdbputimpl(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx, uint8_t hash,  
  12.                          const char *vbuf, int vsiz, int dmode){  
  13.   assert(hdb && kbuf && ksiz >= 0);  
  14.   if(hdb->recc) tcmdbout(hdb->recc, kbuf, ksiz); /*从cache中删除记录*/  
  15.   off_t off = tchdbgetbucket(hdb, bidx); // 从hash数组中取得对应记录的偏移量  
  16.   off_t entoff = 0; // 用于查找冲突数,始终存放冲突树中,当前比较记录的父节点在文件中的偏移量  
  17.   TCHREC rec; // 申请了一个记录结构,用于在内存中存放记录的相关信息  
  18.   char rbuf[HDBIOBUFSIZ];  
  19.   while(off > 0){  
  20.     rec.off = off;  
  21.     if(!tchdbreadrec(hdb, &rec, rbuf)) return false//从数据文件读取指定偏移的记录,即对应hash值的第一个记录  
  22.     // 读取的方式为:首先看是否可以经过mmap映射的内存读,若可以则直接读,否则调用pread读取  
  23.     if(hash > rec.hash){ // 从这里开始比较要插入的值和刚刚读取的记录,比较它们的二级hash值  
  24.       off = rec.left;  
  25.       entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)); // 调整entoff  
  26.     } else if(hash < rec.hash){  
  27.       off = rec.right;  
  28.       entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)) +  
  29.         (hdb->ba64 ? sizeof(uint64_t) : sizeof(uint32_t));  
  30.     } else { // hash值相等,接着比较记录的key  
  31.       if(!rec.kbuf && !tchdbreadrecbody(hdb, &rec)) return false;  
  32.       int kcmp = tcreckeycmp(kbuf, ksiz, rec.kbuf, rec.ksiz); // 比较记录的key值,看是否与已存记录相等  
  33.       if(kcmp > 0){  
  34.         off = rec.left;  
  35.         TCFREE(rec.bbuf);  
  36.         rec.kbuf = NULL;  
  37.         rec.bbuf = NULL;  
  38.         entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)); // 调整entoff  
  39.       } else if(kcmp < 0){  
  40.         off = rec.right;  
  41.         TCFREE(rec.bbuf);  
  42.         rec.kbuf = NULL;  
  43.         rec.bbuf = NULL;  
  44.         entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)) +  
  45.           (hdb->ba64 ? sizeof(uint64_t) : sizeof(uint32_t));  
  46.       } else { // key值相等,在这里说明有相同记录存在,下面根据传入的模式进行相应处理  
  47.         bool rv;  
  48.         int nvsiz;  
  49.         char *nvbuf;  
  50.         HDBPDPROCOP *procptr;  
  51.         switch(dmode){  
  52.           case HDBPDKEEP:  // 保持原记录不变  
  53.             tchdbsetecode(hdb, TCEKEEP, __FILE__, __LINE__, __func__);  
  54.             TCFREE(rec.bbuf);  
  55.             return false;  
  56.           case HDBPDCAT: // 附加到原记录后面  
  57.             if(vsiz < 1){  
  58.               TCFREE(rec.bbuf);  
  59.               return true;  
  60.             }  
  61.             if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)){  
  62.               TCFREE(rec.bbuf);  
  63.               return false;  
  64.             }  
  65.             nvsiz = rec.vsiz + vsiz;  
  66.             if(rec.bbuf){  
  67.               TCREALLOC(rec.bbuf, rec.bbuf, rec.ksiz + nvsiz);  
  68.               memcpy(rec.bbuf + rec.ksiz + rec.vsiz, vbuf, vsiz);  
  69.               rec.kbuf = rec.bbuf;  
  70.               rec.vbuf = rec.kbuf + rec.ksiz;  
  71.               rec.vsiz = nvsiz;  
  72.             } else {  
  73.               TCMALLOC(rec.bbuf, nvsiz + 1);  
  74.               memcpy(rec.bbuf, rec.vbuf, rec.vsiz);  
  75.               memcpy(rec.bbuf + rec.vsiz, vbuf, vsiz);  
  76.               rec.vbuf = rec.bbuf;  
  77.               rec.vsiz = nvsiz;  
  78.             }  
  79.             rv = tchdbwriterec(hdb, &rec, bidx, entoff);  
  80.             TCFREE(rec.bbuf);  
  81.             return rv;  
  82.           case HDBPDADDINT: // 向原记录增加一个整数值,原记录也是整数  
  83.             if(rec.vsiz != sizeof(int)){  
  84.               tchdbsetecode(hdb, TCEKEEP, __FILE__, __LINE__, __func__);  
  85.               TCFREE(rec.bbuf);  
  86.               return false;  
  87.             }  
  88.             if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)){  
  89.               TCFREE(rec.bbuf);  
  90.               return false;  
  91.             }  
  92.             int lnum;  
  93.             memcpy(&lnum, rec.vbuf, sizeof(lnum));  
  94.             if(*(int *)vbuf == 0){  
  95.               TCFREE(rec.bbuf);  
  96.               *(int *)vbuf = lnum;  
  97.               return true;  
  98.             }  
  99.             lnum += *(int *)vbuf;  
  100.             rec.vbuf = (char *)&lnum;  
  101.             *(int *)vbuf = lnum;  
  102.             rv = tchdbwriterec(hdb, &rec, bidx, entoff);  
  103.             TCFREE(rec.bbuf);  
  104.             return rv;  
  105.           case HDBPDADDDBL:  // 向原记录增加一个浮点数值  
  106.             if(rec.vsiz != sizeof(double)){  
  107.               tchdbsetecode(hdb, TCEKEEP, __FILE__, __LINE__, __func__);  
  108.               TCFREE(rec.bbuf);  
  109.               return false;  
  110.             }  
  111.             if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)){  
  112.               TCFREE(rec.bbuf);  
  113.               return false;  
  114.             }  
  115.             double dnum;  
  116.             memcpy(&dnum, rec.vbuf, sizeof(dnum));  
  117.             if(*(double *)vbuf == 0.0){  
  118.               TCFREE(rec.bbuf);  
  119.               *(double *)vbuf = dnum;  
  120.               return true;  
  121.             }  
  122.             dnum += *(double *)vbuf;  
  123.             rec.vbuf = (char *)&dnum;  
  124.             *(double *)vbuf = dnum;  
  125.             rv = tchdbwriterec(hdb, &rec, bidx, entoff);  
  126.             TCFREE(rec.bbuf);  
  127.             return rv;  
  128.           case HDBPDPROC: // 调用回调函数处理原记录  
  129.             if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)){  
  130.               TCFREE(rec.bbuf);  
  131.               return false;  
  132.             }  
  133.             procptr = *(HDBPDPROCOP **)((char *)kbuf - sizeof(procptr));  
  134.             nvbuf = procptr->proc(rec.vbuf, rec.vsiz, &nvsiz, procptr->op);  
  135.             TCFREE(rec.bbuf);  
  136.             if(nvbuf == (void *)-1){  
  137.               return tchdbremoverec(hdb, &rec, rbuf, bidx, entoff);  
  138.             } else if(nvbuf){  
  139.               rec.kbuf = kbuf;  
  140.               rec.ksiz = ksiz;  
  141.               rec.vbuf = nvbuf;  
  142.               rec.vsiz = nvsiz;  
  143.               rv = tchdbwriterec(hdb, &rec, bidx, entoff);  
  144.               TCFREE(nvbuf);  
  145.               return rv;  
  146.             }  
  147.             tchdbsetecode(hdb, TCEKEEP, __FILE__, __LINE__, __func__);  
  148.             return false;  
  149.           default:  
  150.             break;  
  151.         }  
  152.         TCFREE(rec.bbuf);  
  153.         rec.ksiz = ksiz;  
  154.         rec.vsiz = vsiz;  
  155.         rec.kbuf = kbuf;  
  156.         rec.vbuf = vbuf;  
  157.         return tchdbwriterec(hdb, &rec, bidx, entoff);  
  158.       }  
  159.     }  
  160.   }  
  161.   // 执行到这里,说明应写入新记录  
  162.   if(!vbuf){  
  163.     tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);  
  164.     return false;  
  165.   }  
  166.   if(!HDBLOCKDB(hdb)) return false;  
  167.   // 构造文件中的记录格式头部,格式为: magic number(1bytes) + hash value(1bytes)  + left(4bytes or 8bytes) +   
  168.   // right chain(4bytes or 8bytes) + padding size(2bytes)  
  169.   rec.rsiz = hdb->ba64 ? sizeof(uint8_t) * 2 + sizeof(uint64_t) * 2 + sizeof(uint16_t) :  
  170.     sizeof(uint8_t) * 2 + sizeof(uint32_t) * 2 + sizeof(uint16_t);  
  171.   // 键值的存储变量(区域)是变长的,我们在这里计算出其存储区长度,每个字节只用7位,第8位作为符号位     
  172.   if(ksiz < (1U << 7)){  
  173.     rec.rsiz += 1;  
  174.   } else if(ksiz < (1U << 14)){  
  175.     rec.rsiz += 2;  
  176.   } else if(ksiz < (1U << 21)){  
  177.     rec.rsiz += 3;  
  178.   } else if(ksiz < (1U << 28)){  
  179.     rec.rsiz += 4;  
  180.   } else {  
  181.     rec.rsiz += 5;  
  182.   }  
  183.   // value值长度和键值一样,同上  
  184.   if(vsiz < (1U << 7)){  
  185.     rec.rsiz += 1;  
  186.   } else if(vsiz < (1U << 14)){  
  187.     rec.rsiz += 2;  
  188.   } else if(vsiz < (1U << 21)){  
  189.     rec.rsiz += 3;  
  190.   } else if(vsiz < (1U << 28)){  
  191.     rec.rsiz += 4;  
  192.   } else {  
  193.     rec.rsiz += 5;  
  194.   }  
  195.   if(!tchdbfbpsearch(hdb, &rec)){ // 首先看能否从空闲快数组找到合适的记录块,若不能,则标记为应添加到文件末尾  
  196.     HDBUNLOCKDB(hdb);  
  197.     return false;  
  198.   }  
  199.   // 下面几条语句赋予的值将被存放在数据文件的记录中,它们在记录中的顺序和这里赋值的顺序恰好一致,呵呵,不同  
  200.   // 的是,数据文件记录中在这几个值后面还有一个对齐填充区,这样下一个记录就能从对齐字节开始存放了。  
  201.   rec.hash = hash;  
  202.   rec.left = 0;  
  203.   rec.right = 0;  
  204.   rec.ksiz = ksiz;  
  205.   rec.vsiz = vsiz;  
  206.   rec.psiz = 0;  
  207.   rec.kbuf = kbuf;  
  208.   rec.vbuf = vbuf;  
  209.   if(!tchdbwriterec(hdb, &rec, bidx, entoff)){ // 向数据文件写入记录  
  210.     HDBUNLOCKDB(hdb);  
  211.     return false;  
  212.   }  
  213.   // 更新数据文件的统计信息,这里通过向mmap映射的内存区拷贝数据实现,因为我们至少会mmap数据文件控制信息到内存  
  214.   hdb->rnum++;   
  215.   uint64_t llnum = hdb->rnum;  
  216.   llnum = TCHTOILL(llnum);  
  217.   memcpy(hdb->map + HDBRNUMOFF, &llnum, sizeof(llnum));  
  218.   HDBUNLOCKDB(hdb);  
  219.   return true;  
  220. }  
    

    在上面查找冲突树的过程中,由于是先比较二级hash值,找到相等的值,再进行key值的比较,最终决定写入记录的位置,因此对于具有相同二级hash值的记录而言,它们可能会由于key的不同而导致与其它具有不同二级hash值的记录相互混杂,由这里我们可以知道,插入过程构造出来的冲突树是非常杂乱无章的,没有规律可言,这也许能进一步促进冲突数区域平衡吧。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值