“数据不是信息,信息不是知识,知识不是理解。” —— Clifford Stoll
在信息爆炸的时代,我们需要高效管理海量数据的能力。B树家族作为数据库和文件系统的基石,完美平衡了磁盘I/O效率和内存利用率,成为处理大规模数据的首选数据结构。
现代计算机系统中,磁盘访问速度比内存慢10万倍以上。当数据量超过内存容量时,传统二叉搜索树因树高过大导致磁盘I/O次数激增:
数据结构 | 100万数据树高 | 磁盘I/O次数 |
---|---|---|
二叉搜索树 | ~20层 | 20次 |
AVL树 | ~18层 | 18次 |
B树(t=100) | 2-3层 | 2-3次 |
1970年,Rudolf Bayer和Edward M. McCreight发明了B树,核心思想是:
一棵B树T是具有以下性质的有根树:
#define B_TREE_MIN_DEGREE 3
#define MAX_KEYS (2 * B_TREE_MIN_DEGREE - 1)
#define MIN_KEYS (B_TREE_MIN_DEGREE - 1)
#define MAX_CHILDREN (2 * B_TREE_MIN_DEGREE)
typedef struct BTreeNode {
int n; // 当前键数
int keys[MAX_KEYS]; // 键数组
struct BTreeNode *children[MAX_CHILDREN]; // 子节点指针
bool is_leaf; // 是否为叶节点
} BTreeNode;
typedef struct {
BTreeNode *root;
int t; // 最小度数
} BTree;
B树搜索是二叉搜索树的多路推广:
BTreeNode* b_tree_search(BTreeNode *node, int key) {
int i = 0;
while (i < node->n && key > node->keys[i])
i++;
if (i < node->n && key == node->keys[i])
return node;
if (node->is_leaf)
return NULL;
// 从磁盘读取子节点
return b_tree_search(node->children[i], key);
}
搜索路径示例:在t=3的B树中查找38
根节点: [20, 40, 60] → 选择第2子树(20<38≤40)
子节点: [30, 35, 38] → 找到键38
插入操作需要防止节点溢出(>2t-1键),通过分裂和提升保持平衡:
void b_tree_split_child(BTree *tree, BTreeNode *parent, int index) {
BTreeNode *full_child = parent->children[index];
BTreeNode *new_child = b_tree_create_node(full_child->is_leaf);
// 新节点获取后半部分键
new_child->n = tree->t - 1;
for (int j = 0; j < tree->t - 1; j++) {
new_child->keys[j] = full_child->keys[j + tree->t];
}
// 若非叶节点,复制子指针
if (!full_child->is_leaf) {
for (int j = 0; j < tree->t; j++) {
new_child->children[j] = full_child->children[j + tree->t];
}
}
full_child->n = tree->t - 1;
// 调整父节点
for (int j = parent->n; j >= index + 1; j--) {
parent->children[j + 1] = parent->children[j];
}
parent->children[index + 1] = new_child;
for (int j = parent->n - 1; j >= index; j--) {
parent->keys[j + 1] = parent->keys[j];
}
parent->keys[index] = full_child->keys[tree->t - 1];
parent->n++;
}
删除需要防止节点下溢( 对于包含n个键、最小度数为t的B树: 磁盘I/O次数:操作复杂度 O ( h ) = O ( log t n ) O(h) = O(\log_t n) O(h)=O(logtn) MySQL的InnoDB使用B+树组织数据: B树家族通过精心设计的节点结构和平衡算法,在磁盘与内存之间架起了高效的数据桥梁: 下一章预告:斐波那契堆 第十五章我们将探索斐波那契堆——这种神奇的数据结构将带来前所未有的操作效率: 斐波那契堆如同数据结构领域的"瑞士军刀",在特定场景下展现出惊人的性能优势,特别是Dijkstra最短路径算法和Prim最小生成树算法。 B树家族的创新永无止境,从传统关系型数据库到现代分布式存储系统,它们持续为海量数据管理提供可靠高效的解决方案。掌握B树不仅理解了一个数据结构,更获得了处理大规模数据的核心思维模式。合并节点实现
void b_tree_merge(BTree *tree, BTreeNode *node, int index) {
BTreeNode *child = node->children[index];
BTreeNode *sibling = node->children[index + 1];
// 将父节点键下移
child->keys[tree->t - 1] = node->keys[index];
// 复制兄弟键
for (int i = 0; i < sibling->n; i++) {
child->keys[i + tree->t] = sibling->keys[i];
}
// 复制兄弟子指针
if (!child->is_leaf) {
for (int i = 0; i <= sibling->n; i++) {
child->children[i + tree->t] = sibling->children[i];
}
}
// 调整父节点
for (int i = index + 1; i < node->n; i++) {
node->keys[i - 1] = node->keys[i];
}
for (int i = index + 2; i <= node->n; i++) {
node->children[i - 1] = node->children[i];
}
child->n += sibling->n + 1;
node->n--;
free(sibling);
}
14.4 B树的性能分析
14.4.1 空间利用率
最小度数t
最小填充率
最大填充率
2
50%
100%
4
62.5%
87.5%
10
81.8%
90.9%
100
98.0%
99.0%
14.4.2 树高与I/O次数
14.5 B+树:数据库的引擎
14.5.1 B+树与B树的区别
特性
B树
B+树
数据存储
所有节点存储数据
仅叶节点存储数据
键重复
无重复
内部节点键重复
叶节点链接
无链接
叶节点形成双向链表
查找性能
不稳定
稳定(O(log n))
范围查询
低效
高效
空间利用率
较低
更高
14.5.2 B+树节点结构
typedef struct BPlusTreeNode {
int n;
int keys[MAX_KEYS];
union {
struct BPlusTreeNode *children[MAX_CHILDREN]; // 内部节点使用
struct {
void *data_ptrs[MAX_KEYS]; // 叶节点使用
struct BPlusTreeNode *next; // 下一个叶节点
struct BPlusTreeNode *prev; // 上一个叶节点
};
};
bool is_leaf;
} BPlusTreeNode;
14.5.3 B+树范围查询
void b_plus_tree_range_query(BPlusTree *tree, int start, int end) {
BPlusTreeNode *start_node = b_plus_tree_find_leaf(tree, start);
BPlusTreeNode *current = start_node;
int index = 0;
while (current) {
// 找到当前节点中≥start的键
while (index < current->n && current->keys[index] < start)
index++;
// 遍历当前节点
while (index < current->n && current->keys[index] <= end) {
printf("键: %d, 数据地址: %p\n",
current->keys[index],
current->data_ptrs[index]);
index++;
}
if (index >= current->n) {
current = current->next; // 跳转到下一个叶节点
index = 0;
} else {
break;
}
}
}
14.6 B树在数据库中的应用
14.6.1 数据库索引结构
14.6.2 InnoDB存储引擎
// InnoDB索引项结构
typedef struct {
uint32_t page_no; // 页号
uint16_t page_offset; // 页内偏移
uint8_t record_type; // 记录类型
uint8_t info_bits; // 信息位
uint16_t n_fields; // 字段数
// 字段数据...
} innodb_index_entry;
14.6.3 索引优化实践
14.7 B树在文件系统中的应用
14.7.1 现代文件系统对比
文件系统
索引结构
最大文件大小
特点
NTFS
B+树
16EB
Windows主流
ext4
H树(B+树变种)
1EB
Linux主流
XFS
B+树
8EB
高性能
Btrfs
B树
16EB
写时复制
14.7.2 ext4文件系统目录索引
// ext4目录项结构
struct ext4_dir_entry {
__le32 inode; // Inode号
__le16 rec_len; // 目录项长度
__le16 name_len; // 文件名长度
char name[EXT4_NAME_LEN]; // 文件名
};
// 目录索引节点
struct dx_root {
struct dx_countlimit countlimit; // 限制信息
struct dx_entry entries[]; // 索引项
};
14.8 完整B+树实现
#include
14.9 B树变种与创新
14.9.1 现代B树变种
变种
创新点
应用场景
B*树
节点满时先尝试重新分配
数据库系统
Blink树
无锁并发控制
高并发数据库
LSM树
日志结构合并
NoSQL数据库
Fractal树
消息缓冲减少磁盘I/O
高性能存储系统
Bε树
缓冲区优化
流数据处理
14.9.2 LSM树 vs B+树
14.10 总结与下一章预告