文章主要是对我本身实现的B+树的各项指标测试结果展现。B+树的CRUD具体算法文本未涉及,后续可能会补充。c++
引自维基百科github
B+ 树是一种树数据结构,一般用于数据库和操做系统的文件系统中。B+ 树的特色是可以保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+ 树元素自底向上插入,这与二叉树刚好相反。算法
B+树有一个重要的参数叫阶
(m),决定了一颗B+树每个节点存储关键子的个数。数据库
每个节点都会按顺序存储一组关键字,对于非根节点,其关键字树s >= (m + 1) /2。对于叶子节点,其结构中存储指向值的指针,与关键字对应,同时还有一个next指针,指向下一个兄弟叶子节点,所以找到最左叶子节点后能够按关键字顺序遍历;对于非叶子节点,存有s个指向子节点的指针。数组
B+树经过插入时分裂,删除时向兄弟节点借关键字或合并兄弟节点实现平衡,全部的叶子节点都在同一层。查询、插入、删除效率都是Log(N)
。bash
template<typename K, typename V>
class BPTree {
private:
...
public:
// constructor and destructors
...
/** * deserialize from a file */
static BPTree<K, V> deserialize(const std::string &path);
static BPTree<K, V> deserialize(const std::string &path, comparator<K> comp);
void put(const K &key, const V &value);
void remove(K &key);
/** * @return NULL if not exists else a pointer to the value */
V *get(const K &key);
bool containsKey(const K &key);
int getOrder();
int getSize();
/** * iterate order by key * @param func call func(key, value) for each. func returns true means iteration ends */
void foreach(biApply<K, V> func);
void foreachReverse(biApply<K, V> func);
void foreachIndex(biApplyIndex<K, V> func);
void foreachIndexReverse(biApplyIndex<K, V> func);
void serialize(std::string &path);
/** * clear the tree * note that all values allocated will be freed */
void clear();
};
复制代码
Tips:为了兼容自定义类别,须要传入比较大小的函数指针,或实现相应的>、=、<等运算符;数据结构
Node:B+树的索引节点函数
主要数据结构以下:工具
struct Node {
// parent
// if root, parentPtr == NULL
Node *parentPtr = NULL;
// flag
bool leaf;
List<K> keys;
/*-------leaf--------*/
Node *previous = NULL;
Node *next = NULL;
List<V> values;
/*-------index-------*/
List<Node *> childNodePtrs;
// for init
int initCap;
// constructor
...
};
复制代码
List<T>:使用定长数组实现的List,比起std::vector<T>功能更简单,效率更高;内存会在移除必定数量元素后减少。
偏移(byte) | 大小(byte) | 内容 |
---|---|---|
0 | 4 | LYC\0 头部标识 |
4 | 4 | order,int类型,B+树的阶 |
8 | 4 | initCap,int类型,每一个节点预分配大小 |
12 | 4 | size,int类型,元素个数 |
偏移(相对于节点起始,byte) | 大小(byte) | 内容 |
---|---|---|
0 | 4 | leaf,int类型,标识节点是否为叶子节点 |
4 | 4 | sizeofK,int类型,key类型占字节数 |
8 | 4 | kSize,int类型,该节点拥有的关键字数量 |
12 | kSize * sizeofK | 按顺序存储关键字 |
偏移(相对于节点起始,byte) | 大小(byte) | 内容 |
---|---|---|
12 + kSize * sizeofK | 4 | sizeofV,int类型,value类型占字节数 |
16 + kSize * sizeofK | kSize*sizeofK | 按顺序存储值 |
偏移(相对于节点起始,byte) | 大小(byte) | 内容 |
---|---|---|
12 + kSize * sizeofK | kSize * 8 | long类型,按顺序存储字节点在文件中的偏移 |
root
节点没有最少关键字限制,在删除节点操做完成后,须要检查一下root
子节点数量,若是为1,直接将root
的字节点设置为root
,不然删除子节点后可能会形成root
的关键字、子节点丢失。测试环境:
文件main.cpp
有以下宏,1表示开启测试:
// 测试List性能(和vector对比)
#define TEST_LIST 0
// 测试B+树功能正确性
#define TEST_FUNC 0
// 测试B+树的速度(增删查改)
#define TEST_SPEED 0
// 测试B+树的堆使用及内存泄漏(build后使用工具测试)
#define TEST_MEM 0
// 测试B+树的序列化与反序列化
#define TEST_SERIAL 0
复制代码
测试运行结果:
表格:
List(ms) | vector(ms) | |
---|---|---|
尾部插入 | 1.506 | 4.724 |
尾部插入(预分配空间) | 1.201 | 2.765 |
头部插入 | 834.804 | 906.981 |
移除一半元素(头部) | 619.493 | 805.379 |
移除一半元素(尾部) | 1.444 | 7.523 |
rangeRemove(一半) | 0.065 | 0.558 |
柱状图:
测试运行结果:
表格:
bp tree(ms) | stl map(ms) | |
---|---|---|
插入(10^8) | 192808.064 | 325621.333 |
访问(10^8) | 163102.022 | 280150.403 |
移除(10^8) | 213982.406 | 366576.836 |
插入(10^7) | 11825.821 | 22213.139 |
访问(10^7) | 10190.870 | 18137.073 |
移除(10^7) | 15130.015 | 22133.154 |
插入(10^6) | 1057.291 | 1624.615 |
访问(10^6) | 888.186 | 1155.504 |
移除(10^6) | 1099.584 | 1495.433 |
柱状图:
测试运行结果:
测试运行结果:
表格:
数据量 | 序列化(ms) | 反序列化(ms) | 文件大小(bytes) |
---|---|---|---|
0 | 13.794 | 0.082 | 16 |
1 | 0.15 | 0.03 | 40 |
10 | 0.111 | 0.033 | 112 |
10^2 | 0.207 | 0.064 | 1052 |
10^3 | 0.877 | 0.490 | 10168 |
10^4 | 7.870 | 5.567 | 101312 |
10^5 | 61.318 | 59.418 | 1012108 |
10^6 | 694.879 | 477.630 | 10127572 |