文章主要是對我本身實現的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 |