目录
一、磁盘结构分析与数据存储原理
传统的硬盘盘结构是像下面这个样子的,它有一个或多个盘片,用于存储数据。盘片多采用铝合金材料;中间有一个主轴,所有的盘片都绕着这个主轴转动。一个组合臂上面有多个磁头臂,每个磁头臂上面都有一个磁头,负责读写数据。
在磁盘的结构中,盘面 用来保存数据,读取数据要有专业的设备,就是 磁头。像多叠的磁盘,他会有多张盘在里面,每一个盘面存储一定的信息。要读取信息的话磁头要定位到目标的 磁道 上面(圆环上面一圈一圈的同心圆就是磁道,信息实际上也是存在磁道上面的,磁头定位到目标磁道的时间叫做寻道时间),然后等磁盘旋转过程中,磁头从某个 扇区 中读取道数据。
二、为什么会有B树、什么是B树?
多叉树作用是结点数量变少,查找次数减少。一个二叉树节点存在内存里的堆上,通过前后指针关联起来,组织管理方式上是逻辑树的概念。当强查找数据在内存中没找到时,要去磁盘中找,磁盘切换非常耗时,所以通过降低树层高的方式,减少寻找次数、切换时间。即:每一个节点都存在磁盘里,每次对比寻找下一个节点,就是一次磁盘寻址。所以用二叉树对磁盘组织进行寻止,非常耗时。
因此有了多叉树,比如四叉树共有1024个节点,每个节点存3个数据(可插入的位置是3),那么第一层4、第二层16、第三层64、第四层256、第五层1024,层高降为5。
但多叉树不是B树。多叉树只强调开叉,有可能顺着一条路线一直开叉下去,其他叉不用而已。而B树要求所有的叶子节点在同一层。
B树又叫平衡多路二叉树,能够实现动态平衡。B树的每个节点可以表示的信息更多,因此整个树更加的"矮胖",这在从磁盘中查找数据(先读取到内存、后查找)的过程中,可以减少磁盘 IO 的次数,从而提升查找速度。【有种说法:B-树也即B树】
B+树是基于B树的一种变体,有着比B树更高的查询性能。B+树中,节点的每一个关键字代表一个子树的最大值,因此子结点数目等于关键字数目。叶子节点包含了全部的数据,并按照顺序排列,B+树使用链表将他们连起来,这样在查询时效率更快。
三、B树的定义
一棵M阶B树T,要满足以下条件
- 每个结点至多拥有M课子树
- 根结点至少拥有两颗子树
- 除了根结点以外,其余每个分支结点至少拥有M/2课子树
- 所有的叶结点都在同一层上
- 有k棵子树的分支结点则存在k-1个关键字,关键字按照递增顺序进行排序
- 关键字数量满足 ceil( M/2 ) - 1 <= n <= M-1
补充:对M阶的理解,一个"大结点"最多能拥有的子结点的数目就为M,而一个"大结点"内能存放的最多key值个数为M-1。
B树性质:6条可重点归纳为4个维度:
- M;
- M树,key值的关系;
- 所有叶子节点在同一层;
- 每个节点M/2。
四、B树与B+树的区别
- b树所有节点存储数据。
- b+树叶子结点存储数据,通过前后指针链起来;内节点(即非叶子节点)索引用的。【MySQL/mongodb用B+树】。
-
b+树层级更低(更加矮胖),IO次数更少。由于 B+ 树的中间节点不含有实际数据,只有子树的最大数据和子树指针,因此磁盘页中可以容纳更多节点元素,也就是说同样数据情况下,B+ 树会 B 树更加“矮胖”,因此查询效率更快。
-
每次都需要查询到叶子节点,查询性能稳定。B树这时就能体现出优势,由于出现频率较高的树,在B树中往往在上层(非叶子结点),查找到该结点就会成功并结束查询,相对较快。而B+树由于非叶子结点关键字只是代表索引,因此在B+树中,无论查找成功与否,都是走了一条从根到叶子节点的路径。
-
B+树范围查询更加方便。需要查询某个范围内的数据时,由于 B+ 树的叶子节点是一个有序链表,只需在叶子节点上遍历即可,不用像 B 树那样挨个中序遍历比较大小。
五、代码实现
1、节点创建和销毁
typedef int KEY_VALUE;
typedef struct _btree_node {
KEY_VALUE *keys;
struct _btree_node **childrens; //子树
int num; //存储多少数据
int leaf; //是否为叶子节点
} btree_node;
typedef struct _btree {
btree_node *root; //b树 根节点
int t; //每个节点包含值的数量
} btree;
//创建一个节点
btree_node *btree_create_node(int t, int leaf) {
btree_node *node = (btree_node*)calloc(1, sizeof(btree_node));
if (node == NULL) assert(0);
node->leaf = leaf;
node->keys = (KEY_VALUE*)calloc(1, (2*t-1)*sizeof(KEY_VALUE));
node->childrens = (btree_node**)calloc(1, (2*t) * sizeof(btree_node*));
node->num = 0;
return node;
}
//销毁一个节点
void btree_destroy_node(btree_node *node) {
assert(node);
free(node->childrens);
free(node->keys);
free(node);
}
2、分裂插入
btree_split_child(btree *T, btree_node *x, int i) {
int t = T->t;
btree_node *y = x->childrens[i];
btree_node *z = btree_create_node(t, y->leaf);
z->num = t - 1;
int j = 0;
for (j = 0;j < t-1;j ++) {
z->keys[j] = y->keys[j+t];
}
if (y->leaf == 0) { //内节点,不是叶子节点
for (j = 0;j < t;j ++) {
z->childrens[j] = y->childrens[j+t];
}
}
y->num = t - 1;
for (j = x->num;j >= i+1;j --) {
x->childrens[j+1] = x->childrens[j];
}
x->childrens[i+1] = z;
for (j = x->num-1;j >= i;j --) {
x->keys[j+1] = x->keys[j];
}
x->keys[i] = y->keys[t-1];
x->num += 1;
}
//创建空节点
void btree_insert_nonfull(btree *T, btree_node *x, KEY_VALUE k) {
int i = x->num - 1;
if (x->leaf == 1) {
while (i >= 0 && x->keys[i] > k) {
x->keys[i+1] = x->keys[i];
i --;
}
x->keys[i+1] = k;
x->num += 1;
} else {
while (i >= 0 && x->keys[i] > k) i --;
if (x->childrens[i+1]->num == (2*(T->t))-1) {
btree_split_child(T, x, i+1);
if (k > x->keys[i+1]) i++;
}
btree_insert_nonfull(T, x->childrens[i+1], k);
}
}
void btree_insert(btree *T, KEY_VALUE key) {
//int t = T->t;
btree_node *r = T->root;
if (r->num == 2 * T->t - 1) {
btree_node *node = btree_create_node(T->t, 0);
T->root = node;
node->childrens[0] = r;
btree_split_child(T, node, 0);
int i = 0;
if (node->keys[0] < key) i++;
btree_insert_nonfull(T, node->childrens[i], key);
} else {
btree_insert_nonfull(T, r, key);
}
}
3、删除合并
void btree_merge(btree *T, btree_node *node, int idx) {
btree_node *left = node->childrens[idx];
btree_node *right = node->childrens[idx+1];
int i = 0;
/data merge
left->keys[T->t-1] = node->keys[idx];
for (i = 0;i < T->t-1;i ++) {
left->keys[T->t+i] = right->keys[i];
}
if (!left->leaf) {
for (i = 0;i < T->t;i ++) {
left->childrens[T->t+i] = right->childrens[i];
}
}
left->num += T->t;
//destroy right
btree_destroy_node(right);
//node
for (i = idx+1;i < node->num;i ++) {
node->keys[i-1] = node->keys[i];
node->childrens[i] = node->childrens[i+1];
}
node->childrens[i+1] = NULL;
node->num -= 1;
if (node->num == 0) {
T->root = left;
btree_destroy_node(node);
}
}
void btree_delete_key(btree *T, btree_node *node, KEY_VALUE key) {
if (node == NULL) return ;
int idx = 0, i;
while (idx < node->num && key > node->keys[idx]) {
idx ++;
}
if (idx < node->num && key == node->keys[idx]) {
if (node->leaf) {
for (i = idx;i < node->num-1;i ++) {
node->keys[i] = node->keys[i+1];
}
node->keys[node->num - 1] = 0;
node->num--;
if (node->num == 0) { //root
free(node);
T->root = NULL;
}
return ;
} else if (node->childrens[idx]->num >= T->t) {
btree_node *left = node->childrens[idx];
node->keys[idx] = left->keys[left->num - 1];
btree_delete_key(T, left, left->keys[left->num - 1]);
} else if (node->childrens[idx+1]->num >= T->t) {
btree_node *right = node->childrens[idx+1];
node->keys[idx] = right->keys[0];
btree_delete_key(T, right, right->keys[0]);
} else {
btree_merge(T, node, idx);
btree_delete_key(T, node->childrens[idx], key);
}
} else {
btree_node *child = node->childrens[idx];
if (child == NULL) {
printf("Cannot del key = %d\n", key);
return ;
}
if (child->num == T->t - 1) {
btree_node *left = NULL;
btree_node *right = NULL;
if (idx - 1 >= 0)
left = node->childrens[idx-1];
if (idx + 1 <= node->num)
right = node->childrens[idx+1];
if ((left && left->num >= T->t) ||
(right && right->num >= T->t)) {
int richR = 0;
if (right) richR = 1;
if (left && right) richR = (right->num > left->num) ? 1 : 0;
if (right && right->num >= T->t && richR) { //borrow from next
child->keys[child->num] = node->keys[idx];
child->childrens[child->num+1] = right->childrens[0];
child->num ++;
node->keys[idx] = right->keys[0];
for (i = 0;i < right->num - 1;i ++) {
right->keys[i] = right->keys[i+1];
right->childrens[i] = right->childrens[i+1];
}
right->keys[right->num-1] = 0;
right->childrens[right->num-1] = right->childrens[right->num];
right->childrens[right->num] = NULL;
right->num --;
} else { //borrow from prev
for (i = child->num;i > 0;i --) {
child->keys[i] = child->keys[i-1];
child->childrens[i+1] = child->childrens[i];
}
child->childrens[1] = child->childrens[0];
child->childrens[0] = left->childrens[left->num];
child->keys[0] = node->keys[idx-1];
child->num ++;
node->key[idx-1] = left->keys[left->num-1];
left->keys[left->num-1] = 0;
left->childrens[left->num] = NULL;
left->num --;
}
} else if ((!left || (left->num == T->t - 1))
&& (!right || (right->num == T->t - 1))) {
if (left && left->num == T->t - 1) {
btree_merge(T, node, idx-1);
child = left;
} else if (right && right->num == T->t - 1) {
btree_merge(T, node, idx);
}
}
}
btree_delete_key(T, child, key);
}
}
int btree_delete(btree *T, KEY_VALUE key) {
if (!T->root) return -1;
btree_delete_key(T, T->root, key);
return 0;
}