在磁盘存储和内存有序的数据管理中,B 树与 B + 树是核心的数据结构,二者均通过 “多路平衡” 特性减少 IO 次数,但在数据存储方式、查询逻辑上存在本质差异。
一、B 树(Balance Tree):多路平衡搜索树
B 树是 “多路平衡搜索树” 的统称,核心特点是所有节点均可能存储数据,且叶子节点在同一层,保证查询效率稳定。
1. B 树的核心定义(以 m 阶 B 树为例)
“阶(m)” 表示节点最多包含的子节点数,其结构需满足以下规则:
- 每个节点最多存储
m-1
个关键字(按升序排列),对应m
个子节点; - 根节点:若不是叶子,至少有 2 个子节点(关键字数 ≥1);
- 非根非叶节点:至少有
⌈m/2⌉
个子节点(关键字数 ≥⌈m/2⌉ - 1
); - 所有叶子节点位于同一层,无链表连接。
2. B 树结构图示(以 3 阶 B 树为例)
3 阶 B 树的节点最多含 2 个关键字、3 个子节点,非根节点至少含 1 个关键字、2 个子节点,结构如下:
[15] —— 根节点(1个关键字,2个子节点)/ \/ \[5, 10] [20, 25] —— 非叶节点(2个关键字,3个子节点)/ | \ / | \/ | \ / | \
[2,3] [7] [12] [18] [22] [28,30] —— 叶子节点(存储数据,同层)↑ ↑ ↑ ↑ ↑ ↑数据 数据 数据 数据 数据 数据 —— 所有节点均含数据
- 查询逻辑:如查找 “7”,路径为
15 → 5,10 → 7
,找到数据后直接返回(无需到叶子节点); - 特点:随机查询可能提前终止,但范围查询需回溯父节点(如查 “5-20”,需分别遍历左、中分支,效率低)。
二、B + 树(数据库索引首选)
B + 树是 B 树的 “索引 - 数据分离” 变种,核心特点是仅叶子节点存储完整数据,非叶子节点仅存索引关键字,且叶子节点通过链表连接,专为磁盘 IO 和范围查询优化。
1. B + 树的核心定义(以 m 阶 B + 树为例)
基于 B 树扩展,规则差异如下:
- 非叶子节点:仅存储 “索引关键字” 和子节点指针,不存实际数据;
- 叶子节点:存储所有关键字及对应数据(或数据地址),按顺序排列,且通过双向链表连接;
- 所有查询必须到达叶子节点(即使非叶子节点有目标关键字,也需到叶子节点获取数据);
- 非叶子节点的关键字是叶子节点的 “索引副本”(如非叶节点的 “15”,在叶子节点中也存在)。
2. B + 树结构图示(以 3 阶 B + 树为例)
3 阶 B + 树的非叶节点最多含 2 个索引关键字、3 个子节点,叶子节点含数据并通过链表连接,结构如下:
[15, 25] —— 根节点(仅索引,无数据)/ | \/ | \[5, 10] [20, 25] [30, 35] —— 非叶节点(仅索引,无数据)/ | \ / | \ / | \/ | \ / | \ / | \
[2,3] [7] [12,15] [18,20] [22] [28,30] [32] [36,38] —— 叶子节点(存数据)↖ ↗ ↖ ↗ ↖ ↗ ↖ ↗ ↖ ↗————————————————————————————————————双向链表(支持范围查询)
- 查询逻辑:如查找 “7”,路径为
15,25 → 5,10 → 7
(必须到叶子节点); - 范围查询逻辑:如查 “5-20”,找到起始叶子节点 “5” 后,沿双向链表遍历至 “20”,无需回溯父节点,效率极高。
三、B 树与 B + 树的构成步骤及图解
1. B 树的构成步骤
B 树是一种自平衡的多路搜索树,构建过程遵循以下步骤:
步骤 1:确定 B 树的阶数
阶数 m 表示一个节点最多能包含的子节点数量,一个 m 阶 B 树的节点最多包含 m-1 个关键字。
以 3 阶 B 树为例(每个节点最多 2 个关键字,3 个子节点):
节点结构:[关键字1, 关键字2]/ | \子树1 子树2 子树3
在 B 树和 B + 树中,关键字(Key) 是指用于索引和排序的核心数据项,类似于字典中的 “单词”,通过它可以快速定位到对应的数据。
具体来说,关键字有两个核心作用:
- 排序依据:同一节点内的关键字按升序排列,形成有序结构
- 索引功能:通过关键字可以确定数据的存储位置或子树的范围
举例说明:
假设我们用数字作为关键字构建 B 树,存储学生信息(关键字是学号):
- 关键字就是具体的学号:
5、10、15
- 节点中的关键字排序后:
[5, 10, 15]
- 每个关键字对应着具体的学生数据(如姓名、成绩等),或指向存储这些数据的子树
在 B + 树中:
- 非叶节点的关键字:仅作为索引(如
[5, 10]
),用于定位子树 - 叶节点的关键字:既作为索引,又关联着实际数据(如
[5(张三), 10(李四)]
)
简单理解:关键字就像图书馆的书号,通过书号(关键字)可以快速找到对应的书籍(数据),而不需要逐个翻阅。
步骤 2:插入关键字并保持排序
新关键字插入到合适的叶子节点,保持节点内关键字有序:
初始插入5:[5]插入3(小于5,放左侧):[3, 5]插入7(大于5,放右侧):[3, 5, 7] // 此时节点关键字数超过2(3阶B树最大2个),需要分裂
步骤 3:节点分裂(保持平衡)
当节点关键字数超过上限时,中间关键字上移,节点分裂为两个:
分裂[3,5,7]:[5] // 中间关键字上移为父节点/ \
[3] [7] // 分裂为两个子节点
步骤 4:继续插入并调整
插入1、9:[5]/ \
[1,3] [7,9]插入6(会导致右侧节点分裂):[5,7]/ | \[1,3] [6] [9]
2. B + 树的构成步骤
B + 树是 B 树的变种,构建过程略有不同:
步骤 1:确定 B + 树的阶数
与 B 树类似,但非叶子节点仅作为索引,不存储实际数据。
以 3 阶 B + 树为例:
非叶节点(仅索引):[关键字1, 关键字2]/ | \子树1 子树2 子树3叶节点(存数据):[关键字1(数据), 关键字2(数据)]
步骤 2:插入关键字到叶节点
所有关键字都插入到叶节点,保持有序:
插入5、3、7:
叶节点:[3(数据),5(数据),7(数据)] // 超过上限,需要分裂
步骤 3:叶节点分裂并更新索引
叶节点分裂后,将分裂点关键字添加到父节点作为索引:
分裂后:[5] // 父节点(仅索引)/ \
[3(数据)] [5(数据),7(数据)] // 叶节点↖ ↗————链表
步骤 4:继续插入并维护链表
叶节点之间通过链表连接,方便范围查询:
插入1、9、6后:[5,7]/ | \
[1,3] [5,6] [7,9]↖ ↗ ↖ ↗——————————————双向链表
3. B 树与 B + 树构建对比
构建步骤 | B 树 | B + 树 |
---|---|---|
数据存储 | 所有节点都可存储数据 | 仅叶节点存储数据 |
分裂影响 | 可能影响任何层级节点 | 主要影响叶节点,索引随之更新 |
链表结构 | 无链表 | 叶节点通过双向链表连接 |
关键字重复 | 每个关键字只出现一次 | 非叶节点的关键字在叶节点中重复出现 |
B 树构建更简单,适合随机访问;B + 树构建时需维护额外的链表结构,但更适合范围查询和磁盘存储系统。
四、B 树与 B + 树的核心区别
对比维度 | B 树 | B + 树 |
---|---|---|
数据存储位置 | 所有节点(根、非叶、叶子)均存数据 | 仅叶子节点存数据,非叶节点仅存索引 |
查询路径长度 | 不稳定(可能在非叶节点找到数据) | 稳定(必须到叶子节点,路径长度固定) |
范围查询效率 | 低(需回溯父节点,多次遍历分支) | 高(叶子节点双向链表,一次遍历) |
空间利用率 | 低(非叶节点存数据,单次 IO 加载少) | 高(非叶节点仅存索引,单次 IO 加载多) |
数据一致性 | 差(数据分散存储,更新易导致节点分裂) | 好(数据集中在叶子,更新影响小) |
适用场景 | 内存有序数据、少量数据随机访问 | 磁盘存储(数据库索引、文件系统) |
五、B 树与 B + 树在 Java、MySQL 中的应用体现
1. Java 中的应用:B 树变种(红黑树)为主
Java 中无直接的 “标准 B 树” 实现,但核心有序集合依赖红黑树(B 树的 2 阶变种,本质是 “自平衡二叉搜索树”),同时磁盘相关模块隐含 B 树思想。
Java 组件 | 底层结构 | 应用场景与 B 树 / B + 树的关联 |
---|---|---|
TreeMap /TreeSet | 红黑树(2 阶 B 树变种) | - 内存中有序键值对管理,保证O(log n) 的增删改查效率;- 红黑树通过 “平衡” 保证查询稳定,类似 B 树的 “多路平衡” 思想,但仅 2 路分支(适合内存)。 |
java.nio 文件系统 | B 树(部分实现) | - 本地文件系统的索引(如 EXT4)使用 B 树,因文件路径查询多为 “随机访问”,无需频繁范围查询,B 树的提前终止特性更优。 |
2. MySQL 中的应用:B + 树为绝对核心
MySQL 的索引实现与存储引擎强相关,InnoDB(默认) 和 MyISAM 均基于 B + 树,但数据与索引的关联方式不同,且均弃用 B 树(因范围查询需求高频)。
(1)InnoDB 引擎的 B + 树索引
InnoDB 的核心特性是 “聚簇索引”,即主键索引与数据物理存储绑定,结构如下:
- 主键索引(聚簇索引):
- 叶子节点:直接存储完整的行数据(按主键顺序排列);
- 非叶节点:存储主键值 + 子节点指针(仅索引);
- 示例(主键为
id
):
顶层非叶节点[10, 20] / | \/ | \中层非叶节点 中层非叶节点 中层非叶节点[5, 8] [12, 15] [25, 30]/ | \ / | \ / | \/ | \ / | \ / | \叶子节点 叶子节点 叶子节点 ... 叶子节点 叶子节点[id=5, ...] [id=8, ...] [id=12, ...] ... [id=25, ...] [id=30, ...]↗ ↖ ↗ ↖ ↗ ↖ ↗ ↖ ↗ ↖ ————————————————————————————————叶子节点通过双向链表连接
- 非叶节点(顶层、中层):仅存储主键值(如 10、20、5、8 等)和子节点指针,不存储实际行数据,仅用于索引定位
- 叶子节点:存储完整的行数据(包含所有字段),且严格按照主键值升序排列
- 双向链表:所有叶子节点通过链表连接,支持高效的范围查询(如
WHERE id BETWEEN 5 AND 20
)
辅助索引(非聚簇索引):
- 叶子节点:存储 “索引列值 + 主键值”(不存完整数据);
- 查询逻辑:先查辅助索引得到主键,再通过主键索引查完整数据(称为 “回表”);
- 示例(索引列为
name
):
顶层非叶节点[Li, Zhang] / | \/ | \中层非叶节点 中层非叶节点 中层非叶节点[Chen, Han] [Liu, Wang] [Zhao, Zhou]/ | \ / | \ / | \/ | \ / | \ / | \叶子节点 叶子节点 叶子节点 ... 叶子节点 叶子节点
[name=Chen, id=3] [name=Han, id=7] ... [name=Zhao, id=22]↗ ↖ ↗ ↖ ↗ ↖ ————————————————————————————————叶子节点通过双向链表连接
- 非叶节点:存储索引列值(如 Li、Zhang、Chen 等)和子节点指针,用于索引定位
- 叶子节点:存储
索引列值+主键值
(如name=Chen
对应id=3
),不存储完整行数据 - 回表查询:通过辅助索引查询时,需先找到对应主键值,再到聚簇索引中查询完整行数据(这就是 "回表")
- 双向链表:同样支持范围查询(如
WHERE name BETWEEN 'Chen' AND 'Wang'
)
InnoDB 通过这种 B + 树结构实现了索引的高效查询:
- 聚簇索引:直接定位完整数据,适合按主键查询
- 辅助索引:通过 "索引列→主键→完整数据" 的路径查询,适合按非主键字段过滤
- 双向链表:让范围查询无需回溯父节点,大幅提升效率
(2)MyISAM 引擎的 B + 树索引
MyISAM 的索引与数据完全分离(非聚簇索引),结构差异如下:
- 叶子节点:存储 “索引列值 + 数据行的物理地址”(而非主键);
- 查询逻辑:通过索引找到物理地址后,直接到磁盘对应位置读取数据(无需回表);
- 缺点:数据更新可能导致物理地址变化,需同步更新所有索引,效率低于 InnoDB。
(3)MySQL 选择 B + 树的核心原因
- 磁盘 IO 效率高:非叶节点仅存索引,单次 IO 可加载更多关键字(减少 IO 次数,磁盘 IO 是数据库性能瓶颈);
- 范围查询友好:叶子节点双向链表支持
ORDER BY
、BETWEEN
等操作,无需回溯; - 数据集中存储:所有数据在叶子节点,更新时仅需调整叶子节点,一致性更好。
六、总结
- B 树:适合内存中少量有序数据的随机访问(如 Java
TreeMap
的红黑树),但范围查询和磁盘 IO 效率低; - B + 树:通过 “索引 - 数据分离” 和 “叶子链表” 优化,成为数据库索引(MySQL InnoDB/MyISAM)、文件系统的首选,完美适配磁盘 IO 和范围查询需求。