Berkeley DB 1.8.6源代码学习(三)

B

bt_page.c 文件 :辅助文件,用于页面的部分操作;

__bt_free 函数:将一个空闲页放入树的空闲列表;

参数列表:

t :树指针;

h :空闲页指针;

返回值:成功则返回 RET_SUCCESS

伪代码:

h 的左兄弟置无效;

h 的有兄弟指向树的空闲列表头;

将树的空闲列表头指向 h

h 页置位 MPOOL_DIRTY

 

__bt_new 函数:获取一个可用的内存页 新建或从空闲列表中调度;

参数列表:

t :树指针;

pgno :返回获取页面的编号;

返回值:成功则返回页面指针;

伪代码:

如果 t 的空闲链表部位空,则将链表中第一个页面调入缓冲池,并从空闲链表中删除;将调度出的页面编号赋给 pgno 返回,同时返回页面的指针;

如果上述条件不满足,则调用 mpool_new 函数获取一个内存页面;

 

 

bt_utils.c 文件:一些辅助的实用函数库文件;

__bt_ret 函数:用于获取指定记录的信息;

参数列表:

t :树指针;

e :待获取记录的位置信息( EPG );

key :记录键值的返回信息指针;

rkey :记录键值返回信息的实际存储区域;

data :记录数据值的返回信息指针;

rdata :记录数据值返回信息的实际存储区域;

copy :获取记录信息的形式, 1 表示需要复制, 0 表示无须复制,需要注意的是 copy 参数的意义,对于获取记录 e 的相关信息,即可以使用 key data 返回数据的地址,也可以为 rkey rdata 分配内存,将键值和数据值复制的其中返回,其中 copy 1 t 被并发访问防止不可重复读和脏数据的情况,需要使用 rkey rdata 存储返回的数据,此时, key data 的实际指向是 rkey rdata ;如果不使用 key data 可以置空;

返回值:成功、失败;

伪代码

获取记录 e 的节点 bl BLEAF );

如果 key NULL ,表示不用获取键值的信息,直接读取数据部分;

读取键值:

如果 bl 存储的是大数据,则使用 __ovfl_get 函数读取键值,对于大数据,需要复制返回,不能直接使用指针直接访问,所以 __ovfl_get 函数返回后,将 key data 成员变量指向 rkey data 成员变量;然后转去读取数据部分;

如果 copy 为真或 t 被并发访问,则需要将键值保存到 rkey 中,首先检测 rkey 是否有足够的空间保存记录的键值信息,如果不够,需要重新分配内存;然后将键值信息复制到 rkey 中,最后调整 key 的成员变量,使其指向 rkey 的相关成员变量,转去读取数据部分;

如果不是以上情况,则将 bl ksize 赋值给 key size 成员,将 key data 成员指向 bl bytes 部分;完成键值的获取,转向数据部分的获取;

读取数据部分:

同读取键值一样,首先判断,如果 data NULL ,则表示不用获取数据值,返回成功;

如果 bl 存储的是大数据,需要使用 __ovfl_get 函数获取数据值,根据 BLEAF 存储大数据的方式,可知指向数据值存储页面链表首页编号的内存在键值之后,所以在寻址时需要加上键值部分的大小,即 bl ksize ,所以数据寻址的首地址为 bl->bytes + bl->ksize __ovfl_get 函数返回后,同样需要调整 data data 成员变量;

如果 copy 为真或 t 需要并发访问,则需要将数据值存储到 rdata 中返回,基本操作同键值的获取一样,注意 data 的寻址;

如果不是以上情况,则将 data 的成员变量直接指向 t 中存储记录数据值的地址,注意 data 的寻址;

 

__bt_cmp 函数:键值比较函数;

t :树指针;

k1 :指向待比较的值( DBT );

e :被比较的记录位置;

返回值: <0 表示 k1 小于 e =0 表示 k1 等于 e >0 表示 k1 大于 e

伪代码

获取 e 所在的页面 h

如果 e 是最左边的内部节点的第一条记录,则将被本函数判断为小于所有的用户给定的值,这一措施保证我们在用于插入一个当前最小值的时候,不用更新内部结点最左边的记录;(搞不懂,这有什么好处?);即判断 e 的索引值为 0 h 的左兄弟为空,同时 h 不为叶子节点页面,则返回 1

根据 h 是叶子页面还是内部结点页面,使用对应的宏( GETBLEAF GETBINTERNAL )获取 e 的值信息 k2

如果 e 存储的是大数据,则还需要使用 __ovfl_get 函数来获取 e 的值信息;

得到待比较的键值后,调用 t bt_cmp 成员变量来比较 k1 k2 的大小,返回比较的结果,注意, bt_cmp 函数可以由用户指定,缺省的是 __bt_defcmp 函数;

 

__bt_defcmp 函数:缺省的比较函数;

参数列表:

a :待比较的数据值( DBT );

b :待比较的数据值( DBT );

返回值: <0 表示 a<b =0 表示 a=b >0 表示 a>b

伪代码

a b 尺寸较小的设为 len

依次比较 a b len 字符的大小,根据不同处的字符大小决定 a b 的大小;

如果 a b len 个字符相同,则尺寸大的大;

 

__bt_defpfx :缺省的前缀比较函数;

参数列表:

a :待比较的数据值( DBT );

b :待比较的数据值( DBT );

返回值: a b 前缀中相同字符的数量;

伪代码

a b 尺寸较小的设为 len

依次比较 a b len 字符的大小,同时记录已经比较过相等的字符数量 cnt ,到不相等的时候,返回 cnt

如果 a b len 个字符相同,则如果 a 的尺寸小于 b 的尺寸,则返回 a 的尺寸 +1 ;如果 a 的尺寸不小于 b 的尺寸,则返回 a 的尺寸;( a 的尺寸必须小于 b 的尺寸,否则不是本函数使用的情况)

 

 

bt_search.c 文件: 完成查找功能;

__bt_search 函数:从 B 树中查找一个关键字;

参数列表:

t :待操作的树;

key :待查找的关键字;

exactp :查找效果标志, 1 表示找到, 0 表示没有找到;

返回值:成功则返回记录指针 EPG

伪代码:

清除 B 树的所有标志;

从树的根基点(页面)开始查找: pg 初始化为 1

1 、使用 mpool_get 函数获取页面编号为 pg 的页面 h

2 、将树的当前页设为 h ,使用二分查找法在 h 中查找 key

1 )初始化索引数组首地址为 base=0 ,使用 NEXTINDEX 宏获取最后索引数组的大小 lim

2 )将树的当前页的 index 变量赋值为搜索区间中间点的位置,即 base + lim>>1 ;

3) 使用 __bt_cmp 函数比较中间点关键字和 key 的大小;如果相等且 h 是叶子节点,则将 exactp 1 ,返回 B 树的当前页的记录信息;如果相等且 h 为内部结点,则转 6

4 )如果 key 大于中间点关键字,则移动查找区间的首地址,即 base 的值,将中间点位置 +1 赋给 base ;即在原查找区间的高半部分开始新的查找;将 lim 1

5 )将 lim 折半,即 lim>>=1 ;转 2 ,开始在缩小的区间查找;

注意:二分查找中,当 key 小于中间点关键字时,新的查找将在原查找区间的低半部分展开, base 值不变, lim 折半,即 5 )的操作;所以无需讨论小于的情况;

3 、经过二分查找,到达此处的情况就是,当前结点中的关键字没有与 key 匹配的,此时,如果 h 是叶子结点,则转 4 ;否则转 5

4 、需要找到比 key 大的最小的位置;

如果 B 树支持复健,当前的位置处于页面的开始或结束,需要判断临近页面的情况,如果当前位置处于页面开始,且页面的左兄弟页面不为空,则使用 __bt_sprev 函数判断左兄弟页面最后一项纪录与 key 的大小,如果相等,返回左兄弟页面最后一项纪录的位置,同时 exactp 1 ;如果当前位置处于页面结束,则使用 __bt_snext 函数判断右兄弟的第一项纪录与 key 的大小关系,如果相等,则返回右兄弟页面第一项纪录的位置,同时 exactp 1

如果 B 树不支持复键或 B 树支持复健时上述条件不满足,则将 exactp 0 ,放回当前记录位置;

5 、由于计数从 0 开始,及二分查找时的处理方法,此时如果 base 不为 0 ,则需要将 base 1 ;此时得到继续查找页面的入口索引;转 6

6 、将当前页面位置信息推入递归栈中,从而记录查询的路径;

7 、获取继续查找页面的页面编号 pg ;同时将 h 去除 PIN 标记,转 1 ,迭代查找 key

 

__bt_snext 函数:辅助函数,比较右兄弟结点第一项记录与指定关键字的大小;

参数列表:

t :树指针;

h :当前页;

key :待搜索的关键字;

exactp :查找效果标志, 1 表示找到, 0 表示没有找到;

返回值:成功返回 1 ,失败返回 0

伪代码:

获取 h 的右兄弟页面第一条记录的位置 e

使用 __bt_cmp 比较 e key

相等则去除 h PIN 标志,树的当前页面位置置位 e exactp 1 ,返回 1

不相等则将 h 的右兄弟页面去除 PIN 标志,返回 0

 

__bt_sprev 函数:辅助函数,比较左兄弟结点最后一项记录与指定关键字的大小;

参数列表:

t :树指针;

h :当前页;

key :待搜索的关键字;

exactp :查找效果标志, 1 表示找到, 0 表示没有找到;

返回值:成功返回 1 ,失败返回 0

伪代码:

获取 h 的左兄弟页面最后一条记录的位置 e

使用 __bt_cmp 比较 e key

相等则去除 h PIN 标志,树的当前页面位置置位 e exactp 1 ,返回 1

不相等则将 h 的左兄弟页面去除 PIN 标志,返回 0

 

 

bt_overflow.c 文件: 处理大数据的情况;

__ovfl_get 函数:获取大数据

t B 树指针;

p :指向存储有待取大数据数据链的首页编号和大数据的尺寸的数据结构;

ssz :返回大数据的尺寸;

buf :存储取回数据的缓冲区;

bufsz buf 的尺寸;有可能调用函数分配的 buf 不够,本函数可能会重新分配,鉴于此, bufsz 的值可能会被修订;

返回值:成功或失败;

伪代码:

p 指向的结构存储顺序为 pgno u_int32_t 型的数据尺寸;按照这一方式从 p 指向的内存中取出页面编号和数据尺寸存储到 pg sz 中;

sz 赋给 ssz 返回数据的实际大小;

根据 sz 调整缓冲区;

计算一页可以存储的数据尺寸 plen

遍历存储数据的页面链表,将其中存储的数据依次读出存储到 buf 中;

 

__ovfl_put 函数:写入大数据

函数参数:

t B 树指针;

dbt :待存储的数据;

pg :返回存储数据的页面编号;

返回值:成功或失败;

伪代码:

获取一页可以存储的数据容量 plen ,即根据页布局就是页面尺寸减去页面头部的基本信息结构大小;

根据待存储数据的大小和 plen ,循环地将数据存储起来;

1 、数据尚未存储完,则使用 __bt_new 函数获取一个新的页面 h 用于存储;

2 、设置 h 的基本信息,其中 flags 标记为 P_OVERFLOW

3 、将适当的数据存入 h 的数据区,需要注意的是,存储大数据的页面不同于普通的树节点页面,其数据区全部用于存储数据,没有所谓的索引、空闲、数据值的区分;

4 、如果 h 是存储数据的第一个页面,则将其页面编号赋给 pg 返回;否则将其插入到存数数据页链表的尾部;

6 、如果数据存储完,则设置 h MPOOL_DIRTY ;跳出循环;

7 、调整循环相关参数,如将指向待存储数据的指针前移 plen ,将 h 标记为存储数据链表尾;转 1

 

__ovfl_delete 函数:删除大数据

函数参数:

t :树指针;

p :存储数据的页面编号和数据长度信息

返回值:成功或失败

伪代码:

获取存储数据链表首页的编号 pg 和数据尺寸 sz

根据 pg 使用 mpool_get 函数获取存储数据的页面 h

检测 h 是否是 P_PRESERVE 类型(供 B 树内部结点使用的大数据);

如果 h 不是 P_PRESERVE 类型,则遍历链表,将存储数据的页面放回树的空闲页面链表中;

 

bt_close.c 文件: 用于关闭 B 树的文件;

__bt_close 函数:关闭 B 树;

参数列表:

dbp :数据库指针;

返回值:成功或失败;

伪代码:

dbp 中获取 B t

去除 t 中所有的 PIN 页面;

是哟个 __bt_sync 函数同步内存与磁盘;

使用 mpool_close 函数关闭缓冲池;

释放游标中键值所占的内存;

释放 t 结构中 rkey rdata 的值所占的内存;

缓存数据库文件的描述符;

释放 B 树结构所占内存;

释放数据库结构 dbp 所占内存;

关闭数据库文件;

 

__bt_sync 函数:同步缓冲池和磁盘;

参数列表:

dbp :数据库指针;

flags :暂时未发现其用途;

返回值:成功或失败;

伪代码:

去除 PIN 页标记;

如果是内存树或只读树,或 B 树没有修改,则返回成功;

如果数据库的元数据修改,则使用 bt_meta 函数将元数据写回;

使用 mpool_sync 函数将缓冲池中的页面写回文件并去除 B 树的修改标志;

 

bt_meta 函数:写回修改过的元数据;

参数列表:

t B 树指针;

返回值:成功或失败;

伪代码:

将元数据页(第 0 页)读入缓冲池;

B 树当前的元数据信息赋给一个元数据变量 m

m 写入内存中的元数据页,并将此页置为 MPOOL_DIRTY

 

bt_put.c 文件: 向数据库中写入数据

__bt_put 函数:向 B 树中添加一个结点;

参数列表:

dbp :数据库指针,待操作的数据库;

key DBT 指针,传入关键字信息;

data DBT 指针,传入数据信息;

flags :写入标志;

返回值:成功、失败或无定义(关键字已经存在但是写入标志为不可覆盖时);

伪代码:

dbp 中获取 B 树的指针;

取出 PIN 标志;

检查是否是只读树,若是则失败;

如果 flags R_CURSOR ,但是游标没有初始化且没有即将初始化,则返回失败;

处理键值和数据值是大数据的情况:

如果键值是大数据,则使用 __ovfl_put 函数将键值存储到溢出页链表上,将链表首页的页面编号作为键值存储,这在 get 大数据时需要注意,大数据的值实际上是溢出页链表的首页编号;

如果键值(对于键值是大数据的是指处理后的键值即溢出页链表首页的编号)的尺寸加上数据值尺寸大于 B 树的溢出范围,则使用 __ovfl_put 函数将数据值存储到溢出页链表上,将链表首页的页面编号作为数据值存储;

最后,如果经过大数据转移存储处理的键值和数据值尺寸之和仍然超出 B 树的溢出范围,则继续上述处理过程;

处理完待写入的数据,下面开始寻找插入的位置:

如果 flags R_CURSOR ,则获取游标所在的页面 h 和游标的指针 index ;然后使用 __bt_dleaf 函数删除游标指向的记录;

如果 flags 不为 C_CURSOR ,则查找需要插入的位置,

如果判断 B 树的排序方式 bt_order NOT ,或使用 bt_fast 函数搜索 key 失败,则使用 __bt_search 函数搜索 key ,找到待插入的页面 h 和位置 index

如果 flags 置有 R_NOOVERWRITE 标记,且上述查找中找到与 key 相等的记录,则返回未定义;

对于没有置 R_NOOVERWRITE 标记的情况,如果查找到与 key 相等的记录,且 B 树不支持复键,则需要删除找到的记录;

上述操作完成了对插入位置的定位,然后开始插入数据:

首先计算存储数据需要的空间;

如果当前页面的空闲区域( h->upper – h->lower )已经不能存储数据及其索引,则需要将页面分裂,使用 __bt_split 函数完成分裂并插入的操作;然后返回成功;

对于可以插入的情形:

如果 index 不是页面内最后一个索引,即数据不是插入到最后一位,则需要移动插入点以后的索引;同时将页面空闲指针 lower 增加 1 ,即为索引分配内存;

接着在数据区为数据分配内存,同样通过移动 upper 指针完成,将数据保存到分配的内存,注意将这段内存的首地址保存到索引中;

然后检查树的游标是否在本页面中且在插入点之后,则插入完成后需要将游标向前移动一位,这样才能保证游标刚好指向插入前的记录;

如果 B 树的排列顺序为 NOT ,则:

如果 h 的右兄弟节点为空且新插入的记录为本页的最后一条,则将 B 树的排列顺序设为 FORWARD B 树的最后操作页编号改为当前页编号,记录指针改为新插入的记录位置;

否则,如果 h 的左兄弟为空,且新插入的记录为本页第一条记录,则将 B 树排列顺序改为 BACK B 树的最后操作页编号改为当前页编号,记录指针改为新插入的记录位置;

(不知道以上操作的意义,可能是方便连续插入吧,毕竟 bt_fast 函数使用到了 bt_order 成员变量)

h 置已修改标志;

如果 flags 标志置 R_SETCURSOR ,则使用 __bt_setcur 函数将游标置为新插入的记录;

b 树置已修改标志;

返回操作成功;

 

bt_fast 函数:在有序数据(最近操作页面)中快速查找指定数据;

参数列表:

t :数指针;

key :待搜索的键值;

data :待搜索的数据值;

exactp :搜索标志, 1 表示成功, 0 表示失败;

返回值:成功则返回已查找的记录指针 EPG

伪代码:

获取最近插入页面 h

b 树的当前页面设为 h ,位置设为最近插入记录;

计算待插入记录(即搜索的值)的尺寸;

如果 h 当前空闲的空间不能存放待插入的记录,则转 1 (从此处来看,本函数应该是为插入服务的)

如果 B 树的排列顺序为 FORWARD ,且左兄弟页面不为空,当前位置为页面最后一条记录,当 key 等于 B 树的当前记录关键字,则 B 树的最后插入记录位置不变,大于时加 1 ;否则转 1

如果 B 树的排列顺序为 BACK ,且左兄弟页面不为空,当前位置为页面第一条记录,且 key 不小于 B 树的当前记录关键字,则 B 树的最后插入记录位置置 0 ,否则转 1

如果上述比较的结果时相等,则 exactp 1 ,否则等于 0

返回 B 树当前记录;

1 、将 B 树的排列顺序置为 NOT ,去除 h PIN 标记,返回 NULL

 

bt_seq.c 文件: 顺序访问 B 树;

__bt_seq 函数:顺序访问 B 树;

参数列表:

dbp :数据库指针;

key :用于定位和返回键值的 DBT 指针;

data :用于方会数据值的 DBT 指针;

flags :访问的方式,从游标、第一条记录、最后一条记录、下一条、上一条等;

返回值:成功、失败、不确定(没有下一条时)

伪代码:

dbp 中获取 B 树指针 t

去除 t PIN 页面标记;

根据 flags 获取记录位置 e

1 )如果 flags R_NEXT R_PREV ,并且游标已经初始化,则使用 __bt_seqadv 将游标按照 flags 的指示移动一位,同时获取移动后的记录位置 e EPG );如果游标没有初始化,则转 3 );

2 )如果 flags R_FIRST R_LAST R_CURSOR ,则转 3 );

3 )使用 __bt_seqset 函数根据 flags 的指示将游标设定到指定的位置;同时获取定位后的记录位置 e EPG );注意:对于 R_NEXT 将等同于 R_FIRST R_PREV 将等同于 R_LAST

如果成功获取记录位置 e ,则调整游标到记录位置 e ;(当 flags R_FIRST R_LAST R_NEXT R_PREV 时, e 的位置与游标可能不同)

使用 __bt_ret 函数获取 e 的键值和数据值,同时存储到 B 树的 bt_rkey bt_rdata 中;

如果 t 需要同步访问,则将取出 e 所在页面的 PIN 标记;否则,将 e 所在的页面赋给 t bt_pinned 成员变量锁定在内存中;

 

__bt_seqset 函数:根据指定的键值顺序访问 B 树;

参数列表:

t :树指针;

ep :返回顺序访问得到的记录位置 EPG

key :指定的键值;

flags :访问方向和方式;

返回值:成功、失败、不确定(没有下一条记录)

伪代码:

flags R_CURSOR ,则将找到第一条键值为 key 的记录(或大于 key 的最靠前的一条);这样,如果 key 的值为 NULL ,则返回访问失败;否则使用 __bt_first 函数达到访问的目的;

flags R_FIRST R_NEXT ,则返回 B 树的第一条记录:

从根节点开始遍历, pg 为根页面的编号;

1 )获取编号为 pg 的页面 h

2 )如果 h 中记录数为 0 ,则表示 t 为空树,返回不确定;

3 )如果 h 为叶子页面,则转 5 );

4 )取 h 的第一个内结点页面编号赋给 pg ,同时去除 h PIN 标记,转 1 );

5 )将 h 的第一条记录位置赋给 ep ,返回成功;

flags R_LAST R_PREV ,则返回 B 树的最后一条记录:

从根节点开始遍历, pg 为根页面的编号;

1 )获取编号为 pg 的页面 h

2 )如果 h 中记录数为 0 ,则表示 t 为空树,返回不确定;

3 )如果 h 为叶子页面,则转 5 );

4 )取 h 的最后一个内结点页面编号赋给 pg ,同时去除 h PIN 标记,转 1 );

5 )将 h 的最后一条记录位置赋给 ep ,返回成功;

 

__bt_seqadv 函数:提前进行顺序访问;

参数列表:

t :树指针;

ep :返回顺序访问的记录位置;

flags :访问的方向, R_NEXT R_PREV

返回值:成功、失败、不确定(没有下一条记录);

伪代码:

获取树的游标 c

如果 c 被设置为 CURS_ACQUIRE ,则使用 __bt_first 函数定位记录位置,定位的方式就是根据游标的 key 成员变量来定位,如果在树中精确找到键值为 key 的记录,且树不支持复键,则返回这一记录位置,如果支持复键,则返回树中第一条匹配的记录位置;如果找不到,则键值大于 key 最小的记录位置,如果这一位置处于页面的最后一条记录之后,则需要调整到下一页的第一条记录;

获取游标指示的页面 h

如果 flags R_NEXT :如果游标被设置为 CURS_AFTER ,转 1

如果游标指向 h 的最后一条记录,则返回的记录必须是 h 的右兄弟页面的第一条记录,如果 h 的右兄弟页面为空,则返回不确定;如果游标不是指向 h 的最后一条记录,返回 h 页面中游标指定记录的下一条记录;

如果 flags R_PREV :如果游标被设置为 CURS_BEFORE ,转 1

如果游标指向 h 的第一条记录,则返回的记录必须是 h 的左兄弟的最后一条记录,如果 h 的左兄弟页面为空,则返回不确定;如果游标不是指向 h 的第一条记录,返回 h 页面中游标指定记录的前一条记录;

1 、去除游标的 CURS_AFTER CURS_BEFORE 标记,返回游标的当前值;

 

__bt_first 函数:找到指定键值的第一条记录;

参数列表:

t :树指针;

key :指定的键值( DBT );

erval :返回查找的位置;

exactp :指定查找的状态, 1 表示精确匹配, 0 表示未找到指定的键值;

返回值:成功、失败、不确定(指定的键值不可能存在);

伪代码:

使用 __bt_search 函数在 t 中搜索键值为 key 的记录 ep

如果找到键值为 key 的记录:

如果 t 不支持复键,则将 ep 赋给 erval ,返回成功;

如果 t 支持复键,则需要向前遍历,找到复键的第一个记录,方法为:

如果 ep 指向页面的第一条记录,则如果左兄弟为空,表明 ep 肯定是第一条匹配的记录,则返回即可,否则取左兄弟页面的最后一条记录,同时备份当前记录的位置,防止匹配失败时可以回溯;

如果 ep 不是页面第一条记录,则将 ep 指向的记录前移,同时备份当前记录位置;

比较移动后 ep 的键值与 key 的大小,如果相等,则继续上述循环,不断前移;不相等,则将最后备份的记录位置赋给 erval ,返回成功;

如果没有找到键值为 key 的记录,根据 __bt_search 函数的算法, ep 将指向大于键值 key 的第一条记录;如果 ep 指向页面的最后一条记录之后,则需要将其调整为下一页的第一条记录,如果下一页为空,则返回不确定;将 ep 赋给 erval

 

__bt_setcur 函数:设置游标到指定的位置;

参数列表:

t :树指针;

pgno :新游标的页面编号;

index :新游标的记录页面索引;

返回值:无;

伪代码

如果当前游标的 key 成员变量的数据值不为空,则释放其所占的内存;

去除游标上的标记;

将游标的页面指向 pgno ,记录位置指向 index

设置游标的初始化标志;

 

你可能感兴趣的:(数据结构,数据库,null,search,存储,BT)