Berkeley DB 1.8.6源代码学习(七)

来源:互联网 发布:js书籍推荐 编辑:程序博客网 时间:2024/05/02 03:02

函数分析:

文件hash.c

__hash_open:哈希表打开函数,根据数据库文件名和哈希函数信息打开哈希表,函数有6个参数:

file:字符指针,传入数据库文件名;

flags:整型,文件打开标志;

mode:整型,文件打开模式;

infoHASHINFO类型数据,导入哈希表信息;

dflags:整型,没见到使用;

返回值:数据库类型(DB)指针;

伪代码:

如果参数flags标记数据库文件以只写方式打开,则返回失败;不从数据库文件中读取数据不能建立打开哈希表(除非新建);

为哈希表分配表结构HTAB类型变量的内存,使用hashp指针操作这一对象;

hashpfp成员变量初始化为-1

如果file参数为空,则为file参数分配一个临时文件名,并将其赋值给hashpfname成员;

flags参数赋值给hashpflags成员;

如果file参数传入时不为空且数据库文件可读写,则将hashpsave_file成员赋值为真,表示对数据库的变更需要写回磁盘;

按照flagsmode方式打开file文件,并将文件描述符赋给hashpfp成员;

如果是新建表,则使用init_hash函数初始化hashp指向的哈希表基本信息;否则,1、判断info是否有效,且hash成员(哈希函数)不为空,将hashphash函数初始化为infohash成员,否则使用默认的哈希函数,2、使用hget_header函数获取哈希表的头部信息,即hashphdr成员;3、验证取得的头部信息,包括魔数、版本和哈希函数是否匹配;最后根据哈希表头部信息的spares数组判断哈希表位图页的个数bpages,判断的方法就是哈希表最后一个分裂点记录了哈希表溢出页面的总数N,由于位图页从0计数,且为了将最后为使用完的一页计算在内,需要将N加上一个一页包含的位数最大值(即(hashp->hdr.bsize << BYTE_SHIFT) – 1),从而补齐最后一页。然后根据溢出页序号高17位为位图页编号,低15位为位图页内位偏移(从1计数),所以将上述计算的和右移15位,即可得到当前哈希表位图页的个数bpages;这一过程好比使用去尾法找大于数x的最小整数值,如果x等于5.2,则需要将5.2+0.96.2在执行去尾操作,得到6,即大于5.2的最小整数;最后,将bpages赋值给hashpnmaps成员,同时将mapp数组的前bpages项置为0,因为此时没有将位图页读入内存,所以不能为mapp数组项赋值,随着哈希表的使用,mapp将得到位图页的首地址;

初始化缓冲池:计算缓冲池的大小,缓冲的文件对象,最后使用mpool_open函数打开并初始化缓冲池;

对于新建的哈希表,需要初始化哈希表的位图页及相关成员,使用init_htab函数完成;

初始化游标队列;

hashpsplit_buf分配内存,为哈希表扩展时桶分裂作准备;

根据哈希表是否是新表,设置hashpnew_file成员;

为数据库结构dbp分配内存并初始化;

其内部数据存储使用hashp,关闭函数为hash_close,删除函数为hash_delete,获取数据库文件描述服函数为hash_fd,获取函数为hash_get,写入函数为hash_put,顺序访问函数为hash_seq,同步函数为hash_sync,数据库实现类型为哈希表;

返回dbp

 

hash_close函数:哈希表关闭函数;文件内部函数;关闭数据库;

函数参数:

dbp:数据库指针;

返回值:成功或失败;

伪代码:

获取数据库的哈希表hashp

使用hdestroy函数关闭哈希表hashp

释放dbp变量所占内存;

返回hdestroy函数关闭哈希表的结果;

 

hash_fd函数:获取数据库文件描述符;文件内部函数;

函数参数:

dbp:数据库指针;

返回值:数据库文件描述符或失败-1

伪代码:

获取哈希表hashp

如果hashpfp成员不为-1,则返回fp

 

init_hash函数:哈希表初始化函数,文件内部函数;

函数参数:

hashp:哈希表指针;

file:文件名;

infoHASHINFO类型指针,传入创建信息;

返回值:哈希表指针;

伪代码:

首先将hashp中的各成员使用程序缺省的值;

如果file不为NULL,则读取file的基本信息,根据实际文件系统来选择合适的页面大小,从而获得最佳的性能;

根据info传入的信息调整hashp中的成员变量;

返回hashp

 

init_htab函数:初始化哈希表结构,与init_hash函数的区别在于,前者只是初始化一些与硬件相关的信息,而本函数主要侧重于哈希表头部信息的初始化及头部信息关联的HTAB成员的初始化,完成哈希表结构的布局,为页面管理作准备;

函数参数:

hashpHTAB类型指针,传入待初始化的哈希表;

nelem:整型,预计哈希表将要存放元素的数量;

返回值:成功或失败;

伪代码:

本函数首先通过nelem计算出初始化时哈希表的桶数nbuckets,然后根据nbuckets调整spares等头部信息的值,并初始化位图页;

首先,计算nbuckets的值:

前文已述填充因子ffactor表示一个桶中最大存放记录的数量,超过这一数量则哈希表应该扩展,即使扩展时分裂的同不是这个桶,但是这个桶仍然是不正常的,会导致下次对该桶的插入或修改引发哈希表的分裂,即桶中记录的数量可以超过填充因子,但是超过填充因子仍然可以向桶中添加记录;

所以桶哈希表中桶的数量就是元素的数量除以填充因子,这样在理想情况下,如果哈希表真的刚好存储nelem个元素的话,哈希表刚好达到使用的极限而不用扩展(实际元素并不是均匀分布在各个桶中而引发扩展);

为了达到较好的哈希效果和充分利用页面资源,从前文可知,当最大桶号恰好是2的幂-1(即共有2的幂个桶时,桶号从0计数)时,记录在哈希函数上对各个桶号是等概率的,而且从下文可知,如果哈希表不扩展的话,溢出页与桶页之间没有预留页,从而充分利用空间。

所以nbuckets初始化为大于上述计算值的最小2的幂,另外,桶的数量不能低于2,所以nbuckets最小为2,即21次幂;

计算完nbuckets,则哈希表最大桶号为nbuckets1,即头部信息的max_bucket等于nbucktes1,同理可得到高位和低位掩码;根据nbuckets初始化spares数组,假设nbuckets2l2次幂,那么根据最大桶号为nbuckets-1可知,大于最大桶号的最小2的幂为nbuckets,即哈希表的桶页正处于l2-1预留桶页之中,虽然已经将分裂点l2-1预留的空间消耗完,但是并没有计入l2分裂点,所以sparesl2项即spares[l2]保存哈希表从初始化直至第一次扩展之间所有的溢出页面数量,初始化为程序分配了l2+1个空闲的溢出页面,即spares[l2]=l2+1,将ovfl_point初始化为l2,需要注意的是:1、现在处于新建哈希表初始化阶段,桶号已经到达l2-1分裂点,在此之前没有任何溢出页分配,所以spares数组的前l2-1想全部为02、由于哈希表新建,并没有位图页,而位图页实际上也计入到溢出页内,所以spares[l2]中分配的溢出页面中第1页将被用作位图页,实际预留的溢出空闲页只有l2页,这将在后续位图页初始化时体现出来,这是如此,从而导致last_freed成员赋值为2,即序号2之前的溢出页都不空闲(注意,溢出页序号从1计数,否则将于对应分裂点预留的桶页最后一页的页面编号冲突);

计算头部信息占用页面的数量,即计算头部信息hdrpages的值,计算方式是将头部信息尺寸除以页面的大小,注意需要补齐最后一页;hdrpages的作用就是标识哈希表非原数据页的起始页的页面编号,后续桶号、溢出页号转页面编号都是基于这一编号的;

使用__ibitmap函数初始化位图页,从上可知,这里初始化的位图页位于分裂点l2的第1页,同时在位图页中预留l2+1个溢出页,其中第0位置位,表示第1页已经使用(用作本位图页);

返回;

 

hget_header函数:从数据库文件中获取哈希表头部信息;

函数参数:

hashp:哈希表指针;

page_size:页面大小;

返回值:从文件中获取的字节数;

伪代码:

获取hashphdr成员首地址hdt_dest

hashpfd读指针移动到文件开始处;

fp中读HASHHDR尺寸个字节到hdr_dest

如果读取的字节数不等于HASHHDR的尺寸,则返回失败;

返回读取的字节数;

 

hput_header函数:写回头部信息;

函数参数:

hashp:哈希表指针;

返回值:无

伪代码:

获取hashphdr成员首地址whdrp

hashpfp成员读指针移动到文件的开始处;

向文件中写入whdrp地址开始的HASHHDR尺寸个字节;

返回;

 

hdestroy函数:销毁哈希表;文件内部参数;

函数参数:

hashpHTAB指针;

返回值:成功或失败;

伪代码:

使用flush_meta函数将头部信息和位图页信息写回文件;

释放split_bufbigdata_bufbigkey_buf等成员指向的缓冲区;

同步缓冲池到文件;

关闭缓冲池;

关闭fp

判断是否是临时数据库,从而决定是否删除临时文件;

释放hashp结构;

返回;

 

hash_sync函数:将修改的内容写回磁盘;文件内部函数;

函数参数:

dbp:数据库指针;

flags:未使用;

返回值:成功或失败;

伪代码:

获取数据库中哈希表指针;

使用flash_meta写回元数据,使用mpool_sync写回缓冲区更新;

返回操作结果;

 

flush_meta函数:写回元数据,文件内部函数;

函数参数:

hashpHTAB指针;

返回值:成功或失败;

伪代码:

如果哈希表是临时表,则返回;

使用hput_header函数写回头部信息;

遍历mapp数组,将各位图页写回磁盘;

返回;

 

hash_get函数:根据键值获取记录的函数,文件内部函数;

函数参数:

dbpDB类型指针;

keyDBT类型指针,传入键值;

dataDBT类型指针,返回数据值;

flag:整型,未使用;

返回值:成功或失败;

伪代码:

使用hash_access函数获取记录,传入HASH_GET访问类型;

 

hash_put函数:更新或插入记录操作,文件内部函数;

函数参数:

dbpDB类型指针;

keyDBT类型指针,传入记录的键值;

dataDBT类型指针;传入记录的数据值;

flag:操作标记;

返回值:成功或失败;

伪代码:

如果flag不等于0而且不等于R_NOOVERWRITE则返回失败;

如果哈希表是只读的,则返回失败;

如果flag等于R_NOOVERWRITE则传入HASH_PUTNEW标记使用hash_access函数插入记录,否则传入HASH_PUT标记使用hash_access函数更新记录;

返回操作结果;

 

hash_delete函数:删除记录函数,文件内部函数;

dbpDB类型指针;

keyDBT类型,传入待删除记录键值;

flag:未使用;

返回值:成功或失败;

伪代码:

如果哈希表为只读,则返回失败;

通过传入HASH_DELETE标记使用hash_access函数删除记录;

 

hash_access函数:记录访问函数,根据前文所述,关于记录的更新、插入、获取、删除均通过hash_access函数完成,所以本函数需要上述函数完成调用前的判断和预处理,本函数将不作类似处理;

函数参数:

hashpHTAB指针;

actionACTION共用体,用于表示操作的类型--HASH_GET, HASH_PUT, HASH_PUTNEW, HASH_DELETE, HASH_FIRST, HASH_NEXT

keyDBT指针,传入记录键值;

valDBT指针,根据操作类型传入或传出数据值;

返回值:成功或失败;

伪代码:

将桶中本记录前已有记录数num_items初始化为0

如果是更新或插入记录,则计算存储记录需要的空间并保存到item_infoseek_size成员中,由于大数据只需保存索引部分(4字节),普通数据则需要保存整条记录和索引,但是程序中只将记录尺寸赋给seek_size不知何解?如果是其它操作,则无须计算seek_size,赋0即可;

item_infoseek_found_page初始化为0,尚未找到;

调用哈希函数获取桶号bucket

将查找游标cursor初始化,其pagep赋为NULL;使用__get_item_reset函数将游标复位;

cursor的当前桶号置为bucket,即将在bucket桶中查找;

1、使用__get_item_next获取游标当前记录并保存结构到item_info,同时游标下移;

2、如果item_infostatus指示访问失败在返回错误;指示达到桶的最后一条记录则转5;成功获取则转3

3num_items递增;

4、判断获取的记录是否是要找的记录,如果是则转7;否则转1;对于记录的比较,对于大数据,则使用__find_bigpair函数判断;对于普通数据则只用判断二者键值的尺寸和memcpy的比较值;

5、此时完成了对桶bucket中所有记录的访问和比较,没有找到指定的记录;使用__get_item_done函数将cursor作查找完成后的修正;转6

6、由于指定记录没有找到,所以对于更新或插入操作,使用__addel函数将指定记录插入到bucket桶的第num_items条记录处,其实num_items主要作用是为判断本桶的插入操作是否需要引起表扩展,因为num_items此时就是桶中记录的数量;对于获取、删除或其它操作则返回异常;

7、到达此处则表明指定记录已经找到,而且就在bucket桶的第num_items条记录处,同样使用__get_item_donecursor修正;转8

8、如果是插入新的记录操作,则由于表中已经存在此条记录,则返回异常;

如果是获取操作,对于大数据记录,使用__big_return函数获取,对于普通数据直接将查找是获得的值赋给val,返回val

如果是更新操作,则先使用__delpair函数删除原记录,然后使用__addel函数添加新的记录,由于此时num_items不能代表桶中记录的数量,所以使用UNKNOWN参数让__addel自行判断是否需要引起表扩展;

如果是删除操作,则使用__delpair函数删除指定记录;

其它操作则中断程序;

9、返回成功;

 

游标函数:下面将主要介绍几个关于游标的函数,主要用于数据库(哈希表)的顺序遍历操作,没有仔细阅读;

__cursor_creat函数:创建并初始化一个游标;

函数参数:

dbpDB类型指针;

返回值:新生成的游标;

伪代码:

为游标new_curs分配内存;

new_cursinternal成员分配查找记录项的内存;

成员变量和函数初始化;

new_curs推进dbp中哈希表的游标队列;

返回new_curs

 

cursor_get函数:??

 

cursor_delete函数:

 

hash_seq函数:

 

__expand_table函数:扩展哈希表;

函数参数:

hashpHTAB指针;

返回值:成功或失败;

伪代码:

本函数的主要功能是取得新旧桶桶号;同时更新哈希表的头部信息;

首先,将哈希表的最大桶号递增1,同时得到新桶桶号new_bucket

然后获取旧桶桶号,通过前文的分析,知道新桶和旧桶据有最大的低若干位,或者说,在低位掩码下相等;得旧桶桶号old_bucket

使用__new_page函数根据new-buckets为新桶分配页面;

由于最大桶号增加,可能会导致对应分裂点预留的空间消耗完,则判断最大桶号当前所在的分裂点的下一个分裂点spare_ndx,如果spare_ndx大于ovfl_point即当前溢出页面分配所在的分裂点,则需要将溢出页面分裂点移动到下一个分裂点,同时将下一个分裂点对应的spares项即spares[spare_ndx]初始化为当前分裂点的sparesspares[ovfl_point];然后将spare_ndx赋值给ovfl_point,完成溢出页面分配分裂点的转移;

上述操作中需要注意的是:由于分裂点从0计数,而且桶号从0计数,所以spare_ndx在程序中的计算表达式实际是最大桶号分裂点的下一个分裂点,由于桶号的页面使用的是溢出页面预留的值,则二者不会处于同一个分裂点中,溢出页面的分裂点至少比最大桶号的分裂点大1ovfl_point即溢出页面的分裂点,所以,正常情况下,ovfl_point大于等于spare_ndx,一旦spare_ndx大于ovfl_point,则表示最大桶号所在分裂点已经进入到溢出页面分裂点,此时,这一分裂点中不能在继续分配溢出页面,从而转移到下一个分裂点,spare_ndx最多只能大ovfl_point 一个分裂点;spares数组记录对应分裂点时已经分配的溢出页总数,所以有上述赋值操作;

判断是否需要升级掩码;若是则升级掩码;

判断new_bucket的页面编号是否超过系统允许值;

使用__split_page分裂旧桶中的记录;

返回分裂结果;

 

__call_hash函数:哈希函数的接口函数;从前文可知,哈希函数返回32位数,而实际只需要其中的低若干位,本函数实现对哈西函数返回值的过滤;

函数参数:

hashpHTAB指针;

k8字节数指针,传入待计算的键值字符型的指针;

len:整型,键值长度;

返回值:桶号;

伪代码:

调用hashphash成员函数得到哈希值n

使用高位掩码得到高位掩码下的桶号bucket

如果bucket大于最大桶号,则表示需要在低半个表中继续查找;使用地位掩码得到桶号bucket

返回bucket

 

其余剩下一些字节大小端转换函数,大体同B树中类似。

 

原创粉丝点击