﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-那谁的技术博客-随笔分类-tokyo cabinet</title><link>http://www.cppblog.com/converse/category/12481.html</link><description>感兴趣领域:高性能服务器编程,存储,算法,Linux内核</description><language>zh-cn</language><lastBuildDate>Tue, 26 Jan 2010 20:20:43 GMT</lastBuildDate><pubDate>Tue, 26 Jan 2010 20:20:43 GMT</pubDate><ttl>60</ttl><item><title>tokyocabinet1.4.19阅读笔记（五）hash数据库插入数据流程</title><link>http://www.cppblog.com/converse/archive/2010/01/25/106425.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Mon, 25 Jan 2010 15:21:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2010/01/25/106425.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/106425.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2010/01/25/106425.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/106425.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/106425.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: tokyocabinet1.4.19阅读笔记（五）hash数据库插入数据流程&nbsp;&nbsp;<a href='http://www.cppblog.com/converse/archive/2010/01/25/106425.html'>阅读全文</a><img src ="http://www.cppblog.com/converse/aggbug/106425.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/converse/" target="_blank">那谁</a> 2010-01-25 23:21 <a href="http://www.cppblog.com/converse/archive/2010/01/25/106425.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>tokyocabinet1.4.19阅读笔记（四）hash数据库freepool的组织与管理</title><link>http://www.cppblog.com/converse/archive/2010/01/22/106249.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Fri, 22 Jan 2010 14:38:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2010/01/22/106249.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/106249.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2010/01/22/106249.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/106249.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/106249.html</trackback:ping><description><![CDATA[这一节关注freepool的组织,freepool顾名思义,就是负责存放被删除,空闲出来的空间,以便于后面回收利用.<br>在第一节中已经提到,这一个部分,在初始化的时候会全部读入采用malloc从堆中分配的内存中,所以对它的大部分操作都是直接在内存中进行的---除了要同步到数据库文件中时.<br><br>所有的freepool,以数组形式组织在一起,每个freepool元素结构体的定义是:<br>
<div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%;"><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #000000;">typedef&nbsp;</span><span style="color: #0000ff;">struct</span><span style="color: #000000;">&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;type&nbsp;of&nbsp;structure&nbsp;for&nbsp;a&nbsp;free&nbsp;block</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;uint64_t&nbsp;off;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;offset&nbsp;of&nbsp;the&nbsp;block</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;uint32_t&nbsp;rsiz;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;size&nbsp;of&nbsp;the&nbsp;block</span><span style="color: #008000;"><br></span><span style="color: #000000;">}&nbsp;HDBFB;</span></div>
可见,每个freepool关注的仅有两个因素:所保存block在数据库文件中的offset,以及这块block的尺寸.<br><br>当需要插入新的记录时,需要在当前的freepool中进行查询,看有没有适合的freepool可以回收利用,因此需要根据尺寸进行查询,所以为了提高查询速率,freepool数组中的元素是根据每个freepool的尺寸进行排序的,这样根据尺寸进行查找时就可以采用二分查找提高效率了,但是要注意到可能出现的找到的尺寸不符合要求,过大了(大于所需尺寸的一倍以上),这个时候会将这块freepool进行拆分,一部分给予使用,剩余的回收到freepool中.另外,如果在freepool中查找所需尺寸出现了很多次失败的情况(一旦失败表示没有符合要求的freepool可以回收利用,这时就需要增加数据库文件大小以加入新的记录了),就需要对freepool进行一次合并操作,将相邻的freepool合并起来形成尽可能大的freepool,而判断是否相邻的依据就是根据在数据库文件中的offset,此时又会将所有的freepool根据offset进行一次排序,然后再进行前面的合并操作.<br><br>以上就是freepool数组的大体组织情况,因为它保存在内存里面的,而且会经常有更新,那么就会出现当前的freepool与数据库文件中保存的freepool情况不一致的可能,所以在关闭/拷贝数据库的时候还要将内存中的freepool信息一次性的同步到数据库文件中,但是我注意到,在数据库运行期间是没有这个同步操作的,所以,一旦数据库被非法关闭,那么数据库文件中里面的freepool信息将完全的错乱,我想这也是TC不够安全的一个佐证吧.<br><br>下面简单的介绍TC hash数据库中与freepool相关的API:<br>1)static bool tchdbsavefbp(TCHDB *hdb)<br>将当前内存中freepool数组信息同步到数据库文件中,仅当关闭/拷贝数据库时被调用.<br><br>2) static bool tchdbloadfbp(TCHDB *hdb)
<br>加载数据库文件中的freepool信息到内存中,与tchdbsavefbp
是两个互逆的过程.<br><br>3) static void tcfbpsortbyoff(HDBFB *fbpool, int fbpnum)<br>根据offset对freepool数组进行排序<br><br>4) static void tcfbpsortbyrsiz(HDBFB *fbpool, int fbpnum)
<br>根据size对freepool数组进行排序<br><br>5) static void tchdbfbpmerge(TCHDB *hdb)<br>将地址相邻的freepool进行合并,内部实现中首先会调用tcfbpsortbyoff
对freepool根据offset进行排序,这样才方便合并操作.<br><br>6) static void tchdbfbpinsert(TCHDB *hdb, uint64_t off, uint32_t rsiz)
<br>将一块block插入到合适的freepool中,插入之前和插入之后freepool数组都是根据size排序好的.<br><br>7) static bool tchdbfbpsearch(TCHDB *hdb, TCHREC *rec)
<br>根据rec所要求的尺寸,查找一块合适的freepool回收利用,如果找到的freepool过大(大于所要求的一倍),那么就分为两份,一份负责插入rec,一份重新插入到合适的freepool中.<br><br>8) static bool tchdbfbpsplice(TCHDB *hdb, TCHREC *rec, uint32_t nsiz)
<br>查看紧跟着rec的数据库文件空间是否是空闲的,如果是就合并进来,也就是加大rec的尺寸,以满足nsiz大小的要求.<br><br>9) static bool tchdbwritefb(TCHDB *hdb, uint64_t off, uint32_t rsiz)
<br>将一块block置位空闲的(就是写它的magic number为0xb0)<br>
<br>总体来看,freepool是TC hash数据库中操作很频繁的一块数据区,在删除一条记录时需要将这条记录放到合适的freepool中,而新增记录时还需要从当前的freepool中查找合适的block,但是由于freepool是保存在内存中的,而且又进行过排序因此可以使用二分查找算法,所以对它进行的管理操作还是较为高效的.<br><br><br><br>
<br>   <img src ="http://www.cppblog.com/converse/aggbug/106249.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/converse/" target="_blank">那谁</a> 2010-01-22 22:38 <a href="http://www.cppblog.com/converse/archive/2010/01/22/106249.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>tokyocabinet1.4.19阅读笔记（三）hash数据库删除数据流程</title><link>http://www.cppblog.com/converse/archive/2010/01/19/105898.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Tue, 19 Jan 2010 13:18:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2010/01/19/105898.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/105898.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2010/01/19/105898.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/105898.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/105898.html</trackback:ping><description><![CDATA[这一节关注根据key定位到数据进行删除的整个流程。<br><br>先来看这个过程的流程图，其实很简单，包括以下几个按部就班的步骤：<br><img alt="" src="http://www.cppblog.com/images/cppblog_com/converse/12791/r_tokyo%20cabinet%20remove%20key.png"><br><br>a) 首先，根据key查找对应的记录，这个在上一节已经完整的介绍过了，当时也提到，查找操作是后续进行删除和插入新数据时的基础。<br>如果没有找到记录，说明原来就没有，那么就不必继续下去了。<br>假设现在找到了所要删除的数据，接着以下几步：<br>b) 将该记录的magic number置为0xb0，第一节讲解hash数据库概述的时候提到过，每条记录的头部信息中有两种不同magic number，根据这个判断一条记录是否被删除了，现在将这个magic number置为0xb0就是表示这条记录已经被删除了。<br>c) 将这条被删除的记录插入到free pool数组中的合适位置，这是下一节的重点，这里先知道这个操作就好。<br>d) 上一节提到过，同一个bucket index是以二叉树形式组织在一起的，虽然不是平衡的二叉树，但是删除了一个数据之后会破坏二叉树的性质，所以需要在二叉树中找到合适的记录来替换删除这条记录之后剩下的位置。<br>熟悉数据结构与算法的都知道，一个排序二叉树如果按照中序遍历的话，那么是有序的。所以要在删除一个记录之后仍然保持排序二叉树的有序性，是删除操作的重点，下面就是TC中删除一个记录时的调整算法：<br>
<div style="border: 1px solid #cccccc; padding: 4px 5px 4px 4px; background-color: #eeeeee; font-size: 13px; width: 98%;"><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #0000ff;">if rec.left is not null and rec.right is null<br>&nbsp;&nbsp;&nbsp; child = rec.left<br>else if rec.left is null and rec.right is not null<br>&nbsp;&nbsp;&nbsp; child = rec.right<br>else if rec.left is null and rec.right is null<br>&nbsp;&nbsp;&nbsp; child = null<br>else<br>&nbsp;&nbsp;&nbsp; child = rec.left<br>&nbsp;&nbsp;&nbsp; right = rec.right<br>&nbsp;&nbsp; &nbsp;rec.right = child<br>&nbsp;&nbsp;&nbsp; while (rec.right is not null)<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; rec = rec.right<br><br>&nbsp;&nbsp;&nbsp; rec.right = right<br><br>replace rec's original place with child
</span><span style="color: #000000;"><br><br></span></div>
<br>也可以从下图中来理解当删除一个记录时，它的左右子节点都不为空时的处理：<br><img alt="" src="http://www.cppblog.com/images/cppblog_com/converse/12791/r_fix%20binary%20tree.png"><br><br>从图中可以看出，当所要删除的节点左右子节点都不为空时，会去寻找左子树中的最右边的子节点，然后将待删除记录的右子树变成这个最右子节点的右子树。<br><br>需要注意到的是，经典的数据结构算法中，当在排序二叉树中删除一个节点之后，所做的调整与上面的流程有所不同，虽然也是找到的原记录的左子树的最右节点，但是是将这个最右节点直接替换掉原来记录的位置，也就是如下图：<br><img alt="" src="http://www.cppblog.com/images/cppblog_com/converse/12791/r_fix%20binary%20tree2.png"><br><br>所以，这里出现了一个新的问题，TC中的调整算法是有可能导致删除记录之后二叉树不平衡的，那么为什么不选用第二种方法呢？<br>我的理解是：<br>1） 如前一节所述，TC中的二叉树本来就不是必然平衡的，所以TC中的这种调整算法有可能会有&#8220;负负得正&#8221;的结果。<br>2）第二种经典的做法中，需要的调整包括：a）将最右子节点从原来的父节点上删除 b）最右子节点要替换原记录的位置，那么要将原记录的左右子树分别赋值变为最右子节点的左右子树。上面的这个调整，每次调整都是需要修改节点的，而每次修改都会有对磁盘的I/O操作。<br>而第一种做法呢，仅需要一次修改操作-----将原记录的右子树变成最右子节点的右子树即可。<br><br>综合这几个因素，TC选择了I/O较少的做法。<br>我不清楚我的理解是否合理，欢迎补充。<br><br>e）删除了记录，也跳整了树的结构之后，最后的工作就是更新数据库文件header的信息---因为当前记录少了一条。<br><br>最后分析一下整个删除操作的最坏复杂度，还是以1G的bucket对16G的数据库文件记录为例：<br>1）首先查找元素，前面一节说了，需要O(4)次磁盘I/O+O(1)读取内存<br>2）接着置所删除记录的magic number，一次磁盘I/O<br>3）将删除插入到合适的free pool位置，这个下一节会提到，是在内存中进行的。<br>4）调整树结构，在所删除记录左右子树都存在的情况下，首先要找到最右子节点，这又是一个O(4)的磁盘I/O操作，最后将原记录的右子树赋值给最右子节点，又是一次磁盘I/O。不过，上面这个推断与前面是有矛盾的，假如在第一步查找中已经需要O(4)的代价才能定位到所删除元素了，那么最后的这个调整根本没有必要了。<br><br><br>   <img src ="http://www.cppblog.com/converse/aggbug/105898.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/converse/" target="_blank">那谁</a> 2010-01-19 21:18 <a href="http://www.cppblog.com/converse/archive/2010/01/19/105898.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>tokyocabinet1.4.19阅读笔记（二）hash数据库查找key流程</title><link>http://www.cppblog.com/converse/archive/2010/01/12/105500.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Tue, 12 Jan 2010 11:25:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2010/01/12/105500.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/105500.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2010/01/12/105500.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/105500.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/105500.html</trackback:ping><description><![CDATA[这一节关注TC中的hash数据库如何根据一个key查找到该key所在的record,因为后续的删除,插入记录都是以查找为基础的,所以首先描述这部分内容.<br><br>从上一节的<a href="http://www.cppblog.com/converse/archive/2010/01/10/105317.html">概述</a>中,可以看到record结构体中有两个成员left,right:<br>
<div style="border: 1px solid #cccccc; padding: 4px 5px 4px 4px; background-color: #eeeeee; font-size: 13px; width: 98%;"><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #000000;">typedef&nbsp;</span><span style="color: #0000ff;">struct</span><span style="color: #000000;">&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;type&nbsp;of&nbsp;structure&nbsp;for&nbsp;a&nbsp;record</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;uint64_t&nbsp;off;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;offset&nbsp;of&nbsp;the&nbsp;record</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;uint32_t&nbsp;rsiz;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;size&nbsp;of&nbsp;the&nbsp;whole&nbsp;record</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;uint8_t&nbsp;magic;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;magic&nbsp;number</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;uint8_t&nbsp;hash;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;second&nbsp;hash&nbsp;value</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;uint64_t&nbsp;left;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;offset&nbsp;of&nbsp;the&nbsp;left&nbsp;child&nbsp;record</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;uint64_t&nbsp;right;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;offset&nbsp;of&nbsp;the&nbsp;right&nbsp;child&nbsp;record</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;uint32_t&nbsp;ksiz;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;size&nbsp;of&nbsp;the&nbsp;key</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;uint32_t&nbsp;vsiz;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;size&nbsp;of&nbsp;the&nbsp;value</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;uint16_t&nbsp;psiz;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;size&nbsp;of&nbsp;the&nbsp;padding</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;</span><span style="color: #0000ff;">const</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">char</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">kbuf;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;pointer&nbsp;to&nbsp;the&nbsp;key</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;</span><span style="color: #0000ff;">const</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">char</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">vbuf;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;pointer&nbsp;to&nbsp;the&nbsp;value</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;uint64_t&nbsp;boff;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;offset&nbsp;of&nbsp;the&nbsp;body</span><span style="color: #008000;"><br></span><span style="color: #000000;">&nbsp;&nbsp;</span><span style="color: #0000ff;">char</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">bbuf;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">&nbsp;buffer&nbsp;of&nbsp;the&nbsp;body</span><span style="color: #008000;"><br></span><span style="color: #000000;">}&nbsp;TCHREC;</span></div>
说明,每个record是存放在一个类二叉树的结构中的.<br><br>实际上,TC会首先根据一个record的key去算出该key所在的bucket index以及hash index,代码如下:<br>
<div style="border: 1px solid #cccccc; padding: 4px 5px 4px 4px; background-color: #eeeeee; font-size: 13px; width: 98%;"><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;Get&nbsp;the&nbsp;bucket&nbsp;index&nbsp;of&nbsp;a&nbsp;record.<br>&nbsp;&nbsp;&nbsp;`hdb'&nbsp;specifies&nbsp;the&nbsp;hash&nbsp;database&nbsp;object.<br>&nbsp;&nbsp;&nbsp;`kbuf'&nbsp;specifies&nbsp;the&nbsp;pointer&nbsp;to&nbsp;the&nbsp;region&nbsp;of&nbsp;the&nbsp;key.<br>&nbsp;&nbsp;&nbsp;`ksiz'&nbsp;specifies&nbsp;the&nbsp;size&nbsp;of&nbsp;the&nbsp;region&nbsp;of&nbsp;the&nbsp;key.<br>&nbsp;&nbsp;&nbsp;`hp'&nbsp;specifies&nbsp;the&nbsp;pointer&nbsp;to&nbsp;the&nbsp;variable&nbsp;into&nbsp;which&nbsp;the&nbsp;second&nbsp;hash&nbsp;value&nbsp;is&nbsp;assigned.<br>&nbsp;&nbsp;&nbsp;The&nbsp;return&nbsp;value&nbsp;is&nbsp;the&nbsp;bucket&nbsp;index.&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br></span><span style="color: #0000ff;">static</span><span style="color: #000000;">&nbsp;uint64_t&nbsp;tchdbbidx(TCHDB&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">hdb,&nbsp;</span><span style="color: #0000ff;">const</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">char</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">kbuf,&nbsp;</span><span style="color: #0000ff;">int</span><span style="color: #000000;">&nbsp;ksiz,&nbsp;uint8_t&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">hp){<br>&nbsp;&nbsp;assert(hdb&nbsp;</span><span style="color: #000000;">&amp;&amp;</span><span style="color: #000000;">&nbsp;kbuf&nbsp;</span><span style="color: #000000;">&amp;&amp;</span><span style="color: #000000;">&nbsp;ksiz&nbsp;</span><span style="color: #000000;">&gt;=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">&amp;&amp;</span><span style="color: #000000;">&nbsp;hp);<br>&nbsp;&nbsp;uint64_t&nbsp;idx&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">19780211</span><span style="color: #000000;">;<br>&nbsp;&nbsp;uint32_t&nbsp;hash&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">751</span><span style="color: #000000;">;<br>&nbsp;&nbsp;</span><span style="color: #0000ff;">const</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">char</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">rp&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;kbuf&nbsp;</span><span style="color: #000000;">+</span><span style="color: #000000;">&nbsp;ksiz;<br>&nbsp;&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">(ksiz</span><span style="color: #000000;">--</span><span style="color: #000000;">){<br>&nbsp;&nbsp;&nbsp;&nbsp;idx&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;idx&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">37</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">+</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">(uint8_t&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">)kbuf</span><span style="color: #000000;">++</span><span style="color: #000000;">;<br>&nbsp;&nbsp;&nbsp;&nbsp;hash&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;(hash&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">31</span><span style="color: #000000;">)&nbsp;</span><span style="color: #000000;">^</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">(uint8_t&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">)</span><span style="color: #000000;">--</span><span style="color: #000000;">rp;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">hp&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;hash;<br>&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;idx&nbsp;</span><span style="color: #000000;">%</span><span style="color: #000000;">&nbsp;hdb</span><span style="color: #000000;">-&gt;</span><span style="color: #000000;">bnum;<br>}</span></div>
需要特别提醒的一点是,上面的算法中,根据key算出所在的bucket index,是经过模TCHDB-&gt;bnum之后的结果,也就是说,这个值是有限制的---最大不能超过TCHDB初始化时得到的bucket最大数量;而算出的二级hash值,我是没有看出来有数值上的限制的,为什么?看了后面的内容就明白了.<br><br>因此,所有根据记录的key算出bucket index相同的记录全都以二叉树的形式组织起来,而每个bucket array元素存放的整型值就是该bucket树根所在记录的offset.<br>
<br>
到此,相关的结构体联系都清楚了,下面的流程图给出了查找一个key的记录是否存在的流程:
<br><img alt="" src="http://www.cppblog.com/images/cppblog_com/converse/12791/r_tokyo%20cabinet%20get.png"><br><br>简单的解释一下,这个查找的流程就是首先根据查找的key算出所在的bucket,然后在这个bucket的二叉树中按照条件遍历的过程.<br><br>前面提到过,bucket array是整个被mmap映射到共享内存中去的.我们来做一个估计,假设存放bucket array的内存使用了1G,而真正存放record的文件长度有16G,也就是,bucket array的元素与记录大概是1:16的关系,假设所选的hash算法足够的好,以至于每个记录的key可以较为平均的分布在不同的bucket index上,也就是每个bucket array的元素组成的二叉树上平均有16个元素,那么也就最多需要O(4)次读取文件I/O(每次去读取记录的数据都是一次读磁盘操作) + O(1)次内存读操作(因为需要在bucket array中得到树根元素的offset).<br><br>但是等等,上面还有一些细节没有交待清楚.<br><br>首先,上面的二叉树不是类似AVL,红黑树这样的平衡二叉查找树,也就是说,很可能在极端的情况下演变成一个链表---树的一边没有元素,另一边有全部的元素.<br>其次,上面的流程图中还有一点就是每次比较首先比较的是hash值,这个值的奥秘就在于解决上面提到的那个问题.既然只是一个普通的二叉树,无法保证平衡,那么就通过算出这个二级的hash值来保证平衡---当然,前提依然是所选择的hash算法足够的好,可以保证key平均的分布.<br><br>前面提到过,非平衡的二叉树只会在极端的情况下才会演变为一个极端不平衡的二叉树--链表,而诸如AVL,红黑树之类的平衡二叉树,算法编码都相对复杂,调试起来也麻烦,出错了要跟进更麻烦,另外还别忘了,这些平衡二叉树之所以能保持平衡,在删除/增加元素时做的让树重新平衡的操作,比如旋转等,都是要涉及到读写树结点的,而这些,目前都是存放在磁盘上的---也就是这是相对较费时的操作,所以问题在于:是不是值得为这一个极端的情况去优化?另外,引入二级hash就是为了部分解决这个极端不平衡问题,它的思路简单也容易实现,但是引入的另外一个问题就是每次查找时根据key去算bucket index的时候,还要耗费时间去算hash index了.<br><br>平衡点,还是平衡点.时间还是空间,这是一个问题.<br><br>所以,经过对TC的hash数据库查找key流程的分析,最大的感受是:它没有使用复杂的算法与数据结构,而是通过一些巧妙的优化如二级hash的引入,达到了系统效率和编码调试复杂度之间一个较好的平衡.学会"平衡"各种因素,是做项目做事情,都要掌握的一个技能,而这个,只有多经历多想才能慢慢积累了.<br><br>好了,简单的回顾整个查找key的关键点:<br>1) 所有的record是以二叉树的形式组织在同一个bucket上面的.<br>2) 这个二叉树不是平衡的二叉树<br>3) 为了解决问题二造成的极端不平衡问题,TC引入了二级hash,以保证这个二叉树尽可能的平衡.<br><br>以上,就是TC对记录,bucket的组织情况,以及整个查找算法的流程.可以看到,算法,结构体定义等等都不复杂,但是由于巧妙的构思,既可以使用尽可能简单的算法/数据结构,又能规避可能出现的一些隐患,同时还能保证查找的高效率.<br><br>查找是key-value形式存储的核心流程,能够将这个流程优化,对整个系统的性能也有很大的影响.<br><br><br><br>      <img src ="http://www.cppblog.com/converse/aggbug/105500.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/converse/" target="_blank">那谁</a> 2010-01-12 19:25 <a href="http://www.cppblog.com/converse/archive/2010/01/12/105500.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>这是tokyo cabinet的一个BUG么</title><link>http://www.cppblog.com/converse/archive/2009/12/03/102493.html</link><dc:creator>那谁</dc:creator><author>那谁</author><pubDate>Thu, 03 Dec 2009 14:09:00 GMT</pubDate><guid>http://www.cppblog.com/converse/archive/2009/12/03/102493.html</guid><wfw:comment>http://www.cppblog.com/converse/comments/102493.html</wfw:comment><comments>http://www.cppblog.com/converse/archive/2009/12/03/102493.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/converse/comments/commentRss/102493.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/converse/services/trackbacks/102493.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;&nbsp;<a href='http://www.cppblog.com/converse/archive/2009/12/03/102493.html'>阅读全文</a><img src ="http://www.cppblog.com/converse/aggbug/102493.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/converse/" target="_blank">那谁</a> 2009-12-03 22:09 <a href="http://www.cppblog.com/converse/archive/2009/12/03/102493.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>