3-13 B+树
B+树(B+ Tree)
B+树(B+ Tree)是B树(B Tree)的一种变体,由 B树发展而来,其核心区别在于:所有数据仅存储在叶子节点(Leaf Node)中,内部节点(Internal Node)仅存储路由关键字(Routing Key)用于导航。叶子节点之间通过链表(Linked List)串联,使得范围查询(Range Query)极其高效。
B+树是数据库索引和文件系统中最常用的数据结构。MySQL 的 InnoDB 引擎、PostgreSQL、NTFS 和 ext4 文件系统等都使用 B+树作为核心索引结构。
B树 vs B+树结构对比:
B树(数据存储在所有节点中):
[10=D | 20=D]
/ | \
[5=D] [15=D] [25=D | 30=D]
B+树(数据仅在叶子节点,内部节点仅路由):
[10 | 20] ← 内部节点:仅存路由关键字
/ | \
[5,10] -> [15,20] -> [25,30] ← 叶子节点:存数据 + 链表串联
- B树中每个节点都存储数据(用
=D表示) - B+树内部节点只存关键字副本用于路由,所有数据集中在叶子节点
- B+树叶子节点通过
->串联成链表,支持顺序遍历
与B树的区别
B+树在B树基础上做了三个关键改进,使其成为数据库索引的首选结构:
| 特性 | B树(B Tree) | B+树(B+ Tree) |
|---|---|---|
| 数据存储位置 | 所有节点都存储数据 | 仅叶子节点存储数据 |
| 叶子节点链接 | 叶子之间无链接 | 叶子通过链表串联 |
| 关键字重复 | 无重复 | 内部节点路由关键字在叶子中有副本 |
| 范围查询 | 需要中序遍历整棵树 | 找到起点后沿链表顺序遍历 |
| 查找路径 | 可能在内部节点提前终止 | 必须走到叶子节点 |
| 单次查找 | 可能更快(提前命中) | 稍慢(必须到叶子),但更稳定 |
| 节点扇出 | 较小(节点含数据占用空间大) | 较大(内部节点仅存关键字) |
| 顺序遍历 | 需要中序遍历所有层 | 直接遍历叶子链表 |
关键理解:
- B+树内部节点的关键字会在叶子节点中再次出现,因为内部节点的关键字仅用于路由,不携带实际数据
- 链表连接使范围查询从 O(n log n)(B树中序遍历)优化为 O(log n + k)(B+树沿链表遍历 k 个结果)
- 内部节点不存数据意味着同样大小的磁盘页可以容纳更多关键字,树更矮,磁盘 I/O 更少
节点定义
B+树的节点分为两类:内部节点和叶子节点,结构不同。
- 内部节点(Internal Node):存储关键字和子节点指针,用于路由
- 叶子节点(Leaf Node):存储关键字/数据,并通过
next指针串联成链表
以下使用 MAX 表示每个节点最多存储的关键字数量(即阶数 Order)。
C++ 节点定义
#include <iostream>
#include <vector>
using namespace std;
const int MAX = 3; // max keys per node
// B+ Tree node definition
struct BPlusNode {
bool isLeaf; // true if this is a leaf node
vector<int> keys; // keys (routing keys for internal, data for leaf)
// internal node fields
vector<BPlusNode*> children; // child pointers (internal nodes only)
// leaf node fields
BPlusNode* next; // next leaf pointer (leaf nodes only)
// constructor
BPlusNode(bool leaf) : isLeaf(leaf), next(nullptr) {}
};
C++ 使用 vector 存储关键字和子节点指针,isLeaf 标志区分节点类型。内部节点使用 children,叶子节点使用 next 指向下一个叶子节点,形成链表。
C 节点定义
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX 3 // max keys per node
// B+ Tree node definition
typedef struct BPlusNode {
int keys[MAX]; // array of keys
int numKeys; // current number of keys
bool isLeaf; // true if leaf node
// internal node fields
struct BPlusNode* children[MAX + 1]; // child pointers
// leaf node fields
struct BPlusNode* next; // next leaf in linked list
} BPlusNode;
// create a new node
BPlusNode* createNode(bool isLeaf) {
BPlusNode* node = (BPlusNode*)malloc(sizeof(BPlusNode));
node->isLeaf = isLeaf;
node->numKeys = 0;
node->next = NULL;
for (int i = 0; i <= MAX; i++)
node->children[i] = NULL;
return node;
}
C 语言使用固定大小数组存储关键字(最多 MAX 个)和子节点指针(最多 MAX + 1 个),numKeys 记录当前关键字数量。叶子节点通过 next 指针串联。
Python 节点定义
MAX = 3 # max keys per node
class BPlusNode:
def __init__(self, is_leaf=False):
# keys stored as list
self.keys = []
self.is_leaf = is_leaf
# internal node: child pointers
self.children = []
# leaf node: next leaf in linked list
self.next = None
Python 使用列表存储关键字和子节点,is_leaf 区分节点类型。next 指针仅在叶子节点中使用,串联叶子链表。
Go 节点定义
package main
import "fmt"
const maxKeys = 3 // max keys per node
// BPlusNode represents a node in the B+ Tree
type BPlusNode struct {
keys []int // keys (routing keys for internal, data for leaf)
children []*BPlusNode // child pointers (internal nodes only)
isLeaf bool // true if leaf node
next *BPlusNode // next leaf pointer (leaf nodes only)
}
// newBPlusNode creates a new B+ Tree node
func newBPlusNode(isLeaf bool) *BPlusNode {
return &BPlusNode{
keys: []int{},
children: []*BPlusNode{},
isLeaf: isLeaf,
next: nil,
}
}
Go 使用切片存储关键字和子节点指针,isLeaf 标志区分节点类型。内部节点使用 children,叶子节点使用 next 指向下一个叶子节点,形成链表。
搜索操作
B+树的搜索(Search)与B树有一个重要区别:必须到达叶子节点才能确认是否找到目标,因为内部节点只存路由关键字,不存实际数据。
搜索步骤:
- 从根节点开始,在当前节点的关键字中找到合适的区间
- 沿对应的子节点指针向下搜索
- 到达叶子节点后,在叶子中查找目标关键字
搜索 15 的过程:
[10 | 20] ← 10 < 15 < 20, 进入子节点 c[1]
/ | \
[5,10] -> [15,20] -> [25,30] ← 在叶子 [15,20] 中找到 15, 搜索成功
C++ 搜索实现
// search for key k in B+ tree rooted at node
BPlusNode* search(BPlusNode* node, int k, int* index) {
if (node == nullptr) return nullptr;
int i = 0;
// find first key > k (for internal nodes, go left if equal)
while (i < (int)node->keys.size() && k >= node->keys[i])
i++;
if (node->isLeaf) {
// in leaf: check if previous key matches
if (i > 0 && node->keys[i - 1] == k) {
*index = i - 1;
return node;
}
return nullptr;
}
// recurse into appropriate child
return search(node->children[i], k, index);
}
C 搜索实现
// search for key k in B+ tree rooted at node
BPlusNode* search(BPlusNode* node, int k, int* index) {
if (node == NULL) return NULL;
int i = 0;
// find first key > k
while (i < node->numKeys && k >= node->keys[i])
i++;
if (node->isLeaf) {
// in leaf: check if previous key matches
if (i > 0 && node->keys[i - 1] == k) {
*index = i - 1;
return node;
}
return NULL;
}
// recurse into appropriate child
return search(node->children[i], k, index);
}
Python 搜索实现
def search(node, k):
"""Search for key k in B+ tree rooted at node."""
if node is None:
return None, -1
i = 0
# find first key > k
while i < len(node.keys) and k >= node.keys[i]:
i += 1
if node.is_leaf:
# in leaf: check if previous key matches
if i > 0 and node.keys[i - 1] == k:
return node, i - 1
return None, -1
# recurse into appropriate child
return search(node.children[i], k)
Go 搜索实现
// search searches for key k in B+ tree rooted at node
func search(node *BPlusNode, k int) (*BPlusNode, int) {
if node == nil {
return nil, -1
}
i := 0
// find first key > k
for i < len(node.keys) && k >= node.keys[i] {
i++
}
if node.isLeaf {
// in leaf: check if previous key matches
if i > 0 && node.keys[i-1] == k {
return node, i - 1
}
return nil, -1
}
// recurse into appropriate child
return search(node.children[i], k)
}
B+树的搜索必须走到叶子节点,这使得每次搜索的路径长度固定为树高,性能更稳定。搜索的时间复杂度为 O(log n),与B树相同。
范围查询
范围查询(Range Query)是B+树的核心优势。在B树中,范围查询需要中序遍历(Inorder Traversal)整棵树,时间复杂度为 O(n log n);而在B+树中,只需先定位到起始叶子节点,然后沿链表顺序遍历即可,时间复杂度为 O(log n + k),其中 k 是结果数量。
范围查询 [15, 35] 的过程:
1. 搜索 15 → 到达叶子节点 [15, 20]
2. 从 [15, 20] 开始沿链表遍历:
[15, 20] -> [25, 30] -> [35, 40]
3. 收集 [15, 20] 中的 15, 20
4. 收集 [25, 30] 中的 25, 30
5. 收集 [35, 40] 中的 35
6. 到达 40 > 35,停止
结果: 15, 20, 25, 30, 35
C++ 范围查询实现
// range query: find all keys in [lo, hi]
vector<int> rangeQuery(BPlusNode* root, int lo, int hi) {
vector<int> result;
if (root == nullptr) return result;
// find the leaf node containing lo
BPlusNode* node = root;
while (!node->isLeaf) {
int i = 0;
while (i < (int)node->keys.size() && lo >= node->keys[i])
i++;
node = node->children[i];
}
// traverse leaf linked list from starting leaf
while (node != nullptr) {
for (int k : node->keys) {
if (k > hi) return result; // past upper bound
if (k >= lo) result.push_back(k);
}
node = node->next; // follow linked list
}
return result;
}
C 范围查询实现
// range query: print all keys in [lo, hi]
void rangeQuery(BPlusNode* root, int lo, int hi) {
if (root == NULL) return;
// find the leaf node containing lo
BPlusNode* node = root;
while (!node->isLeaf) {
int i = 0;
while (i < node->numKeys && lo >= node->keys[i])
i++;
node = node->children[i];
}
// traverse leaf linked list from starting leaf
int first = 1;
while (node != NULL) {
for (int j = 0; j < node->numKeys; j++) {
if (node->keys[j] > hi) return; // past upper bound
if (node->keys[j] >= lo) {
if (!first) printf(", ");
printf("%d", node->keys[j]);
first = 0;
}
}
node = node->next; // follow linked list
}
}
Python 范围查询实现
def range_query(root, lo, hi):
"""Range query: find all keys in [lo, hi]."""
result = []
if root is None:
return result
# find the leaf node containing lo
node = root
while not node.is_leaf:
i = 0
while i < len(node.keys) and lo >= node.keys[i]:
i += 1
node = node.children[i]
# traverse leaf linked list from starting leaf
while node is not None:
for k in node.keys:
if k > hi:
return result # past upper bound
if k >= lo:
result.append(k)
node = node.next # follow linked list
return result
Go 范围查询实现
// rangeQuery finds all keys in [lo, hi]
func rangeQuery(root *BPlusNode, lo, hi int) []int {
result := []int{}
if root == nil {
return result
}
// find the leaf node containing lo
node := root
for !node.isLeaf {
i := 0
for i < len(node.keys) && lo >= node.keys[i] {
i++
}
node = node.children[i]
}
// traverse leaf linked list from starting leaf
for node != nil {
for _, k := range node.keys {
if k > hi {
return result // past upper bound
}
if k >= lo {
result = append(result, k)
}
}
node = node.next // follow linked list
}
return result
}
范围查询的关键步骤:先通过 O(log n) 的搜索定位起始叶子节点,然后沿链表顺序遍历收集结果。这比B树的中序遍历高效得多,尤其在结果集较大时。
插入操作
B+树的插入(Insert)过程与B树类似,但需要额外维护叶子链表。具体步骤如下:
- 找到目标叶子节点,将关键字插入到正确位置
- 如果叶子节点未满(关键字数 <= MAX),直接插入完成
- 如果叶子节点已满,分裂(Split)为两个叶子节点,将中间关键字提升到父节点
- 更新叶子链表的连接
- 如果父节点也满了,继续向上分裂,可能需要创建新的根节点
插入序列: 10, 20, 5, 15, 25, 30, 35, 40 (MAX = 3)
插入 10: 叶子 [10]
插入 20: 叶子 [10, 20]
插入 5: 叶子 [5, 10, 20]
插入 15: 叶子满,分裂
[10] ← 新内部节点
/ \
[5,10] -> [15,20] ← 叶子链表
插入 25: [5,10] -> [15,20,25]
插入 30: [5,10] -> [15,20,25,30] 满,分裂
[10 | 20] ← 内部节点
/ | \
[5,10] -> [15,20] -> [25,30]
插入 35: [5,10] -> [15,20] -> [25,30,35]
插入 40: [5,10] -> [15,20] -> [25,30,35,40] 满,分裂
[10 | 20 | 30] ← 内部节点满,分裂
[20] ← 新根
/ \
[10] [30]
/ \ | \
[5,10]->[15,20]->[25,30]->[35,40]
C++ 插入实现
// find the leaf node where key k should be inserted
BPlusNode* findLeaf(BPlusNode* root, int k) {
BPlusNode* node = root;
while (!node->isLeaf) {
int i = 0;
while (i < (int)node->keys.size() && k >= node->keys[i])
i++;
node = node->children[i];
}
return node;
}
// insert key into leaf, keeping sorted order
void insertIntoLeaf(BPlusNode* leaf, int k) {
int i = 0;
while (i < (int)leaf->keys.size() && leaf->keys[i] < k)
i++;
leaf->keys.insert(leaf->keys.begin() + i, k);
}
// split a full internal node, return new node and promoted key
struct SplitResult {
BPlusNode* newNode;
int promotedKey;
};
SplitResult splitInternal(BPlusNode* node) {
int mid = node->keys.size() / 2;
int promoted = node->keys[mid];
BPlusNode* newNode = new BPlusNode(false);
// right half goes to new node
newNode->keys.assign(node->keys.begin() + mid + 1, node->keys.end());
newNode->children.assign(node->children.begin() + mid + 1, node->children.end());
// trim original node to left half
node->keys.resize(mid);
node->children.resize(mid + 1);
return {newNode, promoted};
}
// insert key into B+ tree, return new root if tree grew
BPlusNode* insert(BPlusNode* root, int k) {
if (root == nullptr) {
root = new BPlusNode(true);
root->keys.push_back(k);
return root;
}
// find target leaf
BPlusNode* leaf = findLeaf(root, k);
insertIntoLeaf(leaf, k);
// if leaf overflows, split and propagate up
if ((int)leaf->keys.size() > MAX) {
int mid = leaf->keys.size() / 2;
BPlusNode* newLeaf = new BPlusNode(true);
// right half goes to new leaf
newLeaf->keys.assign(leaf->keys.begin() + mid, leaf->keys.end());
// trim original leaf to left half
leaf->keys.resize(mid);
// update linked list: leaf -> newLeaf -> old next
newLeaf->next = leaf->next;
leaf->next = newLeaf;
int promoted = newLeaf->keys[0]; // first key of new leaf
// propagate split upward
BPlusNode* current = leaf;
BPlusNode* sibling = newLeaf;
int keyToInsert = promoted;
// walk up using parent pointers (simplified: rebuild path)
// For this example, we use a bottom-up approach
// In practice, maintain parent pointers or rebuild path
// Here we show the recursive insert approach instead
}
return root;
}
C 插入实现
// find the leaf node where key k should be inserted
BPlusNode* findLeaf(BPlusNode* root, int k) {
BPlusNode* node = root;
while (!node->isLeaf) {
int i = 0;
while (i < node->numKeys && k >= node->keys[i])
i++;
node = node->children[i];
}
return node;
}
// insert key into leaf in sorted order
void insertIntoLeaf(BPlusNode* leaf, int k) {
int i = leaf->numKeys - 1;
while (i >= 0 && leaf->keys[i] > k) {
leaf->keys[i + 1] = leaf->keys[i];
i--;
}
leaf->keys[i + 1] = k;
leaf->numKeys++;
}
// split a full leaf node, return new leaf
BPlusNode* splitLeaf(BPlusNode* leaf) {
BPlusNode* newLeaf = createNode(true);
int mid = (leaf->numKeys + 1) / 2;
// copy right half to new leaf
newLeaf->numKeys = leaf->numKeys - mid;
for (int i = 0; i < newLeaf->numKeys; i++)
newLeaf->keys[i] = leaf->keys[i + mid];
// update linked list
newLeaf->next = leaf->next;
leaf->next = newLeaf;
// trim original leaf
leaf->numKeys = mid;
return newLeaf;
}
Python 插入实现
def find_leaf(root, k):
"""Find the leaf node where key k should be inserted."""
node = root
while not node.is_leaf:
i = 0
while i < len(node.keys) and k >= node.keys[i]:
i += 1
node = node.children[i]
return node
def insert_into_leaf(leaf, k):
"""Insert key into leaf maintaining sorted order."""
i = 0
while i < len(leaf.keys) and leaf.keys[i] < k:
i += 1
leaf.keys.insert(i, k)
def split_leaf(leaf):
"""Split a full leaf node, return (new_leaf, promoted_key)."""
mid = len(leaf.keys) // 2
new_leaf = BPlusNode(is_leaf=True)
# right half goes to new leaf
new_leaf.keys = leaf.keys[mid:]
promoted = new_leaf.keys[0]
# trim original leaf to left half
leaf.keys = leaf.keys[:mid]
# update linked list: leaf -> new_leaf -> old next
new_leaf.next = leaf.next
leaf.next = new_leaf
return new_leaf, promoted
Go 插入实现
// findLeaf finds the leaf node where key k should be inserted
func findLeaf(root *BPlusNode, k int) *BPlusNode {
node := root
for !node.isLeaf {
i := 0
for i < len(node.keys) && k >= node.keys[i] {
i++
}
node = node.children[i]
}
return node
}
// insertIntoLeaf inserts key into leaf maintaining sorted order
func insertIntoLeaf(leaf *BPlusNode, k int) {
i := 0
for i < len(leaf.keys) && leaf.keys[i] < k {
i++
}
// insert at position i
leaf.keys = append(leaf.keys, 0)
copy(leaf.keys[i+1:], leaf.keys[i:])
leaf.keys[i] = k
}
// splitLeaf splits a full leaf node, returns (new_leaf, promoted_key)
func splitLeaf(leaf *BPlusNode) (*BPlusNode, int) {
mid := len(leaf.keys) / 2
newLeaf := newBPlusNode(true)
// right half goes to new leaf
newLeaf.keys = append(newLeaf.keys, leaf.keys[mid:]...)
promoted := newLeaf.keys[0]
// trim original leaf to left half
leaf.keys = leaf.keys[:mid]
// update linked list: leaf -> newLeaf -> old next
newLeaf.next = leaf.next
leaf.next = newLeaf
return newLeaf, promoted
}
插入操作的核心是分裂传播:当一个节点分裂时,中间关键字被提升到父节点;如果父节点也满了,继续向上分裂。叶子节点分裂时还需要更新链表指针。最坏情况下分裂传播到根节点,树高增加一层。
遍历操作
B+树的遍历(Traversal)极为简单:从最左侧的叶子节点开始,沿链表依次访问所有叶子节点即可获得全部关键字的升序排列。这比B树的中序遍历(递归访问子树-关键字-子树)简单得多。
遍历过程:
[10 | 20]
/ | \
[5,10] -> [15,20] -> [25,30]
从最左侧叶子 [5,10] 开始:
[5,10] -> [15,20] -> [25,30]
输出: 5 10 15 20 25 30
C++ 遍历实现
// traverse all keys via leaf linked list (sorted order)
void traverse(BPlusNode* root) {
if (root == nullptr) return;
// find leftmost leaf
BPlusNode* node = root;
while (!node->isLeaf)
node = node->children[0];
// walk the linked list
while (node != nullptr) {
for (int k : node->keys)
cout << k << " ";
node = node->next;
}
}
// print tree structure level by level
void printTree(BPlusNode* node, int level) {
if (node == nullptr) return;
cout << "Level " << level << ": [";
for (int i = 0; i < (int)node->keys.size(); i++) {
if (i > 0) cout << " | ";
cout << node->keys[i];
}
cout << "]" << (node->isLeaf ? " (leaf)" : "") << endl;
if (!node->isLeaf) {
for (auto child : node->children)
printTree(child, level + 1);
}
}
C 遍历实现
// traverse all keys via leaf linked list (sorted order)
void traverse(BPlusNode* root) {
if (root == NULL) return;
// find leftmost leaf
BPlusNode* node = root;
while (!node->isLeaf)
node = node->children[0];
// walk the linked list
int first = 1;
while (node != NULL) {
for (int i = 0; i < node->numKeys; i++) {
if (!first) printf(" ");
printf("%d", node->keys[i]);
first = 0;
}
node = node->next;
}
printf("\n");
}
// print leaf nodes with links
void printLeaves(BPlusNode* root) {
if (root == NULL) return;
// find leftmost leaf
BPlusNode* node = root;
while (!node->isLeaf)
node = node->children[0];
// print each leaf with link arrows
while (node != NULL) {
printf("[");
for (int i = 0; i < node->numKeys; i++) {
if (i > 0) printf(",");
printf("%d", node->keys[i]);
}
printf("]");
if (node->next != NULL) printf(" -> ");
node = node->next;
}
printf("\n");
}
Python 遍历实现
def traverse(root):
"""Traverse all keys via leaf linked list (sorted order)."""
if root is None:
return
# find leftmost leaf
node = root
while not node.is_leaf:
node = node.children[0]
# walk the linked list
result = []
while node is not None:
for k in node.keys:
result.append(k)
node = node.next
return result
def print_leaves(root):
"""Print leaf nodes with link arrows."""
if root is None:
return
# find leftmost leaf
node = root
while not node.is_leaf:
node = node.children[0]
# print each leaf with link arrows
parts = []
while node is not None:
parts.append(str(node.keys))
node = node.next
print(" -> ".join(parts))
Go 遍历实现
// traverse walks all keys via leaf linked list (sorted order)
func traverse(root *BPlusNode) []int {
if root == nil {
return nil
}
// find leftmost leaf
node := root
for !node.isLeaf {
node = node.children[0]
}
// walk the linked list
result := []int{}
for node != nil {
result = append(result, node.keys...)
node = node.next
}
return result
}
// printLeaves prints leaf nodes with link arrows
func printLeaves(root *BPlusNode) {
if root == nil {
return
}
// find leftmost leaf
node := root
for !node.isLeaf {
node = node.children[0]
}
// print each leaf with link arrows
first := true
for node != nil {
if !first {
fmt.Print(" -> ")
}
first = false
fmt.Print("[")
for i, k := range node.keys {
if i > 0 {
fmt.Print(",")
}
fmt.Print(k)
}
fmt.Print("]")
node = node.next
}
fmt.Println()
}
遍历操作直接沿叶子链表前进,无需递归,时间复杂度为 O(n)。这种设计使得顺序读取全部数据非常高效,特别适合数据库的全表扫描(Full Table Scan)和索引扫描(Index Scan)。
完整实现
以下提供 C++、C、Python 三种语言的 B+树完整实现,包含搜索、范围查询、插入和遍历功能。为了代码的可读性和教学目的,实现中使用简化的递归插入方式。
C++ 完整实现
#include <iostream>
#include <vector>
using namespace std;
const int MAX = 3; // max keys per node
struct BPlusNode {
bool isLeaf;
vector<int> keys;
vector<BPlusNode*> children;
BPlusNode* next;
BPlusNode(bool leaf) : isLeaf(leaf), next(nullptr) {}
};
// find the leaf where key k resides
BPlusNode* findLeaf(BPlusNode* root, int k) {
BPlusNode* node = root;
while (!node->isLeaf) {
int i = 0;
while (i < (int)node->keys.size() && k >= node->keys[i])
i++;
node = node->children[i];
}
return node;
}
// search for key k
bool search(BPlusNode* root, int k) {
if (root == nullptr) return false;
BPlusNode* leaf = findLeaf(root, k);
for (int key : leaf->keys)
if (key == k) return true;
return false;
}
// insert key into B+ tree, return (new_root, new_sibling_if_split)
pair<BPlusNode*, BPlusNode*> insertInternal(BPlusNode* node, int k);
// insert into leaf node, handling overflow
pair<BPlusNode*, BPlusNode*> insertLeaf(BPlusNode* leaf, int k) {
// insert key in sorted order
int i = 0;
while (i < (int)leaf->keys.size() && leaf->keys[i] < k)
i++;
leaf->keys.insert(leaf->keys.begin() + i, k);
// if no overflow, done
if ((int)leaf->keys.size() <= MAX) {
return {nullptr, nullptr};
}
// split leaf: left half stays, right half goes to new leaf
int mid = leaf->keys.size() / 2;
BPlusNode* newLeaf = new BPlusNode(true);
newLeaf->keys.assign(leaf->keys.begin() + mid, leaf->keys.end());
leaf->keys.resize(mid);
// maintain linked list
newLeaf->next = leaf->next;
leaf->next = newLeaf;
return {nullptr, newLeaf};
}
// insert into internal node, handling overflow
pair<BPlusNode*, BPlusNode*> insertInternal(BPlusNode* node, int k) {
// find child to descend into
int i = 0;
while (i < (int)node->keys.size() && k >= node->keys[i])
i++;
BPlusNode* child = node->children[i];
pair<BPlusNode*, BPlusNode*> result;
// recurse: insert into child
if (child->isLeaf)
result = insertLeaf(child, k);
else
result = insertInternal(child, k);
BPlusNode* splitNode = result.second;
if (splitNode == nullptr) {
return {nullptr, nullptr}; // no split occurred
}
// child split: insert promoted key and new child into this node
int promotedKey = splitNode->keys[0];
node->keys.insert(node->keys.begin() + i, promotedKey);
node->children.insert(node->children.begin() + i + 1, splitNode);
// if no overflow, done
if ((int)node->keys.size() <= MAX) {
return {nullptr, nullptr};
}
// split internal node
int mid = node->keys.size() / 2;
int promotedUp = node->keys[mid];
BPlusNode* newNode = new BPlusNode(false);
newNode->keys.assign(node->keys.begin() + mid + 1, node->keys.end());
newNode->children.assign(node->children.begin() + mid + 1, node->children.end());
node->keys.resize(mid);
node->children.resize(mid + 1);
// return new internal node; use keys[0] trick for promoted key
newNode->keys.insert(newNode->keys.begin(), promotedUp);
return {nullptr, newNode};
}
// main insert function
BPlusNode* insert(BPlusNode* root, int k) {
if (root == nullptr) {
root = new BPlusNode(true);
root->keys.push_back(k);
return root;
}
// try inserting into tree
pair<BPlusNode*, BPlusNode*> result;
if (root->isLeaf)
result = insertLeaf(root, k);
else
result = insertInternal(root, k);
// if root split, create new root
if (result.second != nullptr) {
BPlusNode* newRoot = new BPlusNode(false);
newRoot->keys.push_back(result.second->keys[0]);
newRoot->children.push_back(root);
newRoot->children.push_back(result.second);
root = newRoot;
}
return root;
}
// traverse all keys via leaf linked list
void traverse(BPlusNode* root) {
if (root == nullptr) return;
BPlusNode* node = root;
while (!node->isLeaf)
node = node->children[0];
bool first = true;
while (node != nullptr) {
for (int k : node->keys) {
if (!first) cout << " ";
cout << k;
first = false;
}
node = node->next;
}
cout << endl;
}
// print leaf nodes with links
void printLeaves(BPlusNode* root) {
if (root == nullptr) return;
BPlusNode* node = root;
while (!node->isLeaf)
node = node->children[0];
while (node != nullptr) {
cout << "[";
for (int i = 0; i < (int)node->keys.size(); i++) {
if (i > 0) cout << ",";
cout << node->keys[i];
}
cout << "]";
if (node->next != nullptr) cout << " -> ";
node = node->next;
}
cout << endl;
}
// range query: find all keys in [lo, hi]
void rangeQuery(BPlusNode* root, int lo, int hi) {
if (root == nullptr) return;
BPlusNode* node = findLeaf(root, lo);
bool first = true;
while (node != nullptr) {
for (int k : node->keys) {
if (k > hi) {
cout << endl;
return;
}
if (k >= lo) {
if (!first) cout << " ";
cout << k;
first = false;
}
}
node = node->next;
}
cout << endl;
}
int main() {
BPlusNode* root = nullptr;
// insert sequence
int keys[] = {10, 20, 5, 15, 25, 30, 35, 40};
cout << "Insert sequence: ";
for (int k : keys) {
cout << k << " ";
root = insert(root, k);
}
cout << endl << endl;
// show leaf structure
cout << "Leaf nodes (linked list): ";
printLeaves(root);
// traverse all keys
cout << "Traversal: ";
traverse(root);
// search
cout << "Search 15: " << (search(root, 15) ? "Found" : "Not found") << endl;
cout << "Search 12: " << (search(root, 12) ? "Found" : "Not found") << endl;
// range query
cout << "Range query [15, 35]: ";
rangeQuery(root, 15, 35);
return 0;
}
C 完整实现
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX 3
typedef struct BPlusNode {
int keys[MAX + 1]; // extra slot for temporary overflow
int numKeys;
bool isLeaf;
struct BPlusNode* children[MAX + 2]; // extra slot for overflow
struct BPlusNode* next; // leaf linked list
} BPlusNode;
BPlusNode* createNode(bool isLeaf) {
BPlusNode* node = (BPlusNode*)malloc(sizeof(BPlusNode));
node->isLeaf = isLeaf;
node->numKeys = 0;
node->next = NULL;
for (int i = 0; i <= MAX + 1; i++)
node->children[i] = NULL;
return node;
}
// find the leaf where key k resides
BPlusNode* findLeaf(BPlusNode* root, int k) {
BPlusNode* node = root;
while (!node->isLeaf) {
int i = 0;
while (i < node->numKeys && k >= node->keys[i])
i++;
node = node->children[i];
}
return node;
}
// search for key k
bool search(BPlusNode* root, int k) {
if (root == NULL) return false;
BPlusNode* leaf = findLeaf(root, k);
for (int i = 0; i < leaf->numKeys; i++)
if (leaf->keys[i] == k) return true;
return false;
}
// split result: promoted key + new sibling node
typedef struct {
int promotedKey;
BPlusNode* sibling;
} SplitResult;
// forward declaration
SplitResult insertIntoNode(BPlusNode* node, int k);
// insert into leaf, return split result
SplitResult insertIntoLeaf(BPlusNode* leaf, int k) {
// insert key in sorted order
int i = leaf->numKeys - 1;
while (i >= 0 && leaf->keys[i] > k) {
leaf->keys[i + 1] = leaf->keys[i];
i--;
}
leaf->keys[i + 1] = k;
leaf->numKeys++;
SplitResult sr = {0, NULL};
// if no overflow, done
if (leaf->numKeys <= MAX) return sr;
// split leaf
int mid = leaf->numKeys / 2;
BPlusNode* newLeaf = createNode(true);
newLeaf->numKeys = leaf->numKeys - mid;
for (int j = 0; j < newLeaf->numKeys; j++)
newLeaf->keys[j] = leaf->keys[j + mid];
// maintain linked list
newLeaf->next = leaf->next;
leaf->next = newLeaf;
leaf->numKeys = mid;
sr.promotedKey = newLeaf->keys[0];
sr.sibling = newLeaf;
return sr;
}
// insert into internal node, return split result
SplitResult insertIntoNode(BPlusNode* node, int k) {
// find child to descend into
int i = 0;
while (i < node->numKeys && k >= node->keys[i])
i++;
BPlusNode* child = node->children[i];
SplitResult sr;
if (child->isLeaf)
sr = insertIntoLeaf(child, k);
else
sr = insertIntoNode(child, k);
if (sr.sibling == NULL) {
// no split
SplitResult empty = {0, NULL};
return empty;
}
// child split: insert promoted key and sibling into this node
int j = node->numKeys - 1;
while (j >= i) {
node->keys[j + 1] = node->keys[j];
node->children[j + 2] = node->children[j + 1];
j--;
}
node->keys[i] = sr.promotedKey;
node->children[i + 1] = sr.sibling;
node->numKeys++;
SplitResult result = {0, NULL};
// if no overflow, done
if (node->numKeys <= MAX) return result;
// split internal node
int mid = node->numKeys / 2;
BPlusNode* newNode = createNode(false);
newNode->numKeys = node->numKeys - mid - 1;
for (int j = 0; j < newNode->numKeys; j++)
newNode->keys[j] = node->keys[j + mid + 1];
for (int j = 0; j <= newNode->numKeys; j++)
newNode->children[j] = node->children[j + mid + 1];
result.promotedKey = node->keys[mid];
result.sibling = newNode;
node->numKeys = mid;
return result;
}
// main insert function
BPlusNode* insert(BPlusNode* root, int k) {
if (root == NULL) {
root = createNode(true);
root->keys[0] = k;
root->numKeys = 1;
return root;
}
SplitResult sr;
if (root->isLeaf)
sr = insertIntoLeaf(root, k);
else
sr = insertIntoNode(root, k);
// if root split, create new root
if (sr.sibling != NULL) {
BPlusNode* newRoot = createNode(false);
newRoot->keys[0] = sr.promotedKey;
newRoot->children[0] = root;
newRoot->children[1] = sr.sibling;
newRoot->numKeys = 1;
return newRoot;
}
return root;
}
// traverse via leaf linked list
void traverse(BPlusNode* root) {
if (root == NULL) return;
BPlusNode* node = root;
while (!node->isLeaf)
node = node->children[0];
int first = 1;
while (node != NULL) {
for (int i = 0; i < node->numKeys; i++) {
if (!first) printf(" ");
printf("%d", node->keys[i]);
first = 0;
}
node = node->next;
}
printf("\n");
}
// print leaf nodes with links
void printLeaves(BPlusNode* root) {
if (root == NULL) return;
BPlusNode* node = root;
while (!node->isLeaf)
node = node->children[0];
while (node != NULL) {
printf("[");
for (int i = 0; i < node->numKeys; i++) {
if (i > 0) printf(",");
printf("%d", node->keys[i]);
}
printf("]");
if (node->next != NULL) printf(" -> ");
node = node->next;
}
printf("\n");
}
// range query: print all keys in [lo, hi]
void rangeQuery(BPlusNode* root, int lo, int hi) {
if (root == NULL) return;
BPlusNode* node = findLeaf(root, lo);
int first = 1;
while (node != NULL) {
for (int i = 0; i < node->numKeys; i++) {
if (node->keys[i] > hi) {
printf("\n");
return;
}
if (node->keys[i] >= lo) {
if (!first) printf(" ");
printf("%d", node->keys[i]);
first = 0;
}
}
node = node->next;
}
printf("\n");
}
int main() {
BPlusNode* root = NULL;
printf("Insert sequence: ");
int keys[] = {10, 20, 5, 15, 25, 30, 35, 40};
int n = sizeof(keys) / sizeof(keys[0]);
for (int i = 0; i < n; i++) {
printf("%d ", keys[i]);
root = insert(root, keys[i]);
}
printf("\n\n");
printf("Leaf nodes (linked list): ");
printLeaves(root);
printf("Traversal: ");
traverse(root);
printf("Search 15: %s\n", search(root, 15) ? "Found" : "Not found");
printf("Search 12: %s\n", search(root, 12) ? "Found" : "Not found");
printf("Range query [15, 35]: ");
rangeQuery(root, 15, 35);
return 0;
}
Python 完整实现
MAX = 3 # max keys per node
class BPlusNode:
def __init__(self, is_leaf=False):
self.keys = []
self.children = []
self.is_leaf = is_leaf
self.next = None # leaf linked list
def find_leaf(root, k):
"""Find the leaf node where key k should reside."""
node = root
while not node.is_leaf:
i = 0
while i < len(node.keys) and k >= node.keys[i]:
i += 1
node = node.children[i]
return node
def search(root, k):
"""Search for key k in B+ tree."""
if root is None:
return False
leaf = find_leaf(root, k)
return k in leaf.keys
def _insert_leaf(leaf, k):
"""Insert key into leaf node. Return (promoted_key, new_sibling) or (None, None)."""
# insert in sorted order
i = 0
while i < len(leaf.keys) and leaf.keys[i] < k:
i += 1
leaf.keys.insert(i, k)
# no overflow
if len(leaf.keys) <= MAX:
return None, None
# split leaf
mid = len(leaf.keys) // 2
new_leaf = BPlusNode(is_leaf=True)
new_leaf.keys = leaf.keys[mid:]
leaf.keys = leaf.keys[:mid]
# maintain linked list
new_leaf.next = leaf.next
leaf.next = new_leaf
return new_leaf.keys[0], new_leaf
def _insert_internal(node, k):
"""Insert key into internal node. Return (promoted_key, new_sibling) or (None, None)."""
# find child to descend into
i = 0
while i < len(node.keys) and k >= node.keys[i]:
i += 1
child = node.children[i]
if child.is_leaf:
promoted, sibling = _insert_leaf(child, k)
else:
promoted, sibling = _insert_internal(child, k)
if sibling is None:
return None, None # no split
# child split: insert promoted key and sibling into this node
node.keys.insert(i, promoted)
node.children.insert(i + 1, sibling)
# no overflow
if len(node.keys) <= MAX:
return None, None
# split internal node
mid = len(node.keys) // 2
promoted_up = node.keys[mid]
new_node = BPlusNode(is_leaf=False)
new_node.keys = node.keys[mid + 1:]
new_node.children = node.children[mid + 1:]
node.keys = node.keys[:mid]
node.children = node.children[:mid + 1]
return promoted_up, new_node
def insert(root, k):
"""Insert key k into B+ tree, return new root."""
if root is None:
root = BPlusNode(is_leaf=True)
root.keys.append(k)
return root
# insert and check for root split
if root.is_leaf:
promoted, sibling = _insert_leaf(root, k)
else:
promoted, sibling = _insert_internal(root, k)
if sibling is not None:
# root split: create new root
new_root = BPlusNode(is_leaf=False)
new_root.keys = [promoted]
new_root.children = [root, sibling]
return new_root
return root
def traverse(root):
"""Traverse all keys via leaf linked list."""
if root is None:
return []
node = root
while not node.is_leaf:
node = node.children[0]
result = []
while node is not None:
result.extend(node.keys)
node = node.next
return result
def print_leaves(root):
"""Print leaf nodes with link arrows."""
if root is None:
return
node = root
while not node.is_leaf:
node = node.children[0]
parts = []
while node is not None:
parts.append(str(node.keys))
node = node.next
print(" -> ".join(parts))
def range_query(root, lo, hi):
"""Range query: find all keys in [lo, hi]."""
if root is None:
return []
node = find_leaf(root, lo)
result = []
while node is not None:
for k in node.keys:
if k > hi:
return result
if k >= lo:
result.append(k)
node = node.next
return result
if __name__ == '__main__':
root = None
# insert sequence
keys = [10, 20, 5, 15, 25, 30, 35, 40]
print("Insert sequence:", " ".join(str(k) for k in keys))
for k in keys:
root = insert(root, k)
print()
# show leaf structure
print("Leaf nodes (linked list): ", end="")
print_leaves(root)
# traverse all keys
print("Traversal:", " ".join(str(k) for k in traverse(root)))
# search
print(f"Search 15: {'Found' if search(root, 15) else 'Not found'}")
print(f"Search 12: {'Found' if search(root, 12) else 'Not found'}")
# range query
result = range_query(root, 15, 35)
print("Range query [15, 35]:", " ".join(str(k) for k in result))
Go 完整实现
package main
import "fmt"
const maxKeys = 3 // max keys per node
// BPlusNode represents a node in the B+ Tree
type BPlusNode struct {
keys []int
children []*BPlusNode
isLeaf bool
next *BPlusNode
}
func newBPlusNode(isLeaf bool) *BPlusNode {
return &BPlusNode{
keys: []int{},
children: []*BPlusNode{},
isLeaf: isLeaf,
next: nil,
}
}
// findLeaf finds the leaf node where key k should reside
func findLeaf(root *BPlusNode, k int) *BPlusNode {
node := root
for !node.isLeaf {
i := 0
for i < len(node.keys) && k >= node.keys[i] {
i++
}
node = node.children[i]
}
return node
}
// bpSearch searches for key k in B+ tree
func bpSearch(root *BPlusNode, k int) bool {
if root == nil {
return false
}
leaf := findLeaf(root, k)
for _, key := range leaf.keys {
if key == k {
return true
}
}
return false
}
// insertLeaf inserts key into leaf node, returns (promoted_key, new_sibling)
func insertLeaf(leaf *BPlusNode, k int) (int, *BPlusNode) {
// insert in sorted order
i := 0
for i < len(leaf.keys) && leaf.keys[i] < k {
i++
}
leaf.keys = append(leaf.keys, 0)
copy(leaf.keys[i+1:], leaf.keys[i:])
leaf.keys[i] = k
// no overflow
if len(leaf.keys) <= maxKeys {
return 0, nil
}
// split leaf
mid := len(leaf.keys) / 2
newLeaf := newBPlusNode(true)
newLeaf.keys = append(newLeaf.keys, leaf.keys[mid:]...)
leaf.keys = leaf.keys[:mid]
// maintain linked list
newLeaf.next = leaf.next
leaf.next = newLeaf
return newLeaf.keys[0], newLeaf
}
// insertInternal inserts key into internal node, returns (promoted_key, new_sibling)
func insertInternal(node *BPlusNode, k int) (int, *BPlusNode) {
// find child to descend into
i := 0
for i < len(node.keys) && k >= node.keys[i] {
i++
}
child := node.children[i]
var promoted int
var sibling *BPlusNode
if child.isLeaf {
promoted, sibling = insertLeaf(child, k)
} else {
promoted, sibling = insertInternal(child, k)
}
if sibling == nil {
return 0, nil // no split
}
// child split: insert promoted key and sibling into this node
node.keys = append(node.keys, 0)
copy(node.keys[i+1:], node.keys[i:])
node.keys[i] = promoted
node.children = append(node.children, nil)
copy(node.children[i+2:], node.children[i+1:])
node.children[i+1] = sibling
// no overflow
if len(node.keys) <= maxKeys {
return 0, nil
}
// split internal node
mid = len(node.keys) / 2
promotedUp := node.keys[mid]
newNode := newBPlusNode(false)
newNode.keys = append(newNode.keys, node.keys[mid+1:]...)
newNode.children = append(newNode.children, node.children[mid+1:]...)
node.keys = node.keys[:mid]
node.children = node.children[:mid+1]
return promotedUp, newNode
}
// bpInsert inserts key k into B+ tree, returns new root
func bpInsert(root *BPlusNode, k int) *BPlusNode {
if root == nil {
root = newBPlusNode(true)
root.keys = append(root.keys, k)
return root
}
var promoted int
var sibling *BPlusNode
if root.isLeaf {
promoted, sibling = insertLeaf(root, k)
} else {
promoted, sibling = insertInternal(root, k)
}
if sibling != nil {
// root split: create new root
newRoot := newBPlusNode(false)
newRoot.keys = append(newRoot.keys, promoted)
newRoot.children = append(newRoot.children, root, sibling)
return newRoot
}
return root
}
// bpTraverse traverses all keys via leaf linked list
func bpTraverse(root *BPlusNode) []int {
if root == nil {
return nil
}
node := root
for !node.isLeaf {
node = node.children[0]
}
result := []int{}
for node != nil {
result = append(result, node.keys...)
node = node.next
}
return result
}
// bpPrintLeaves prints leaf nodes with link arrows
func bpPrintLeaves(root *BPlusNode) {
if root == nil {
return
}
node := root
for !node.isLeaf {
node = node.children[0]
}
first := true
for node != nil {
if !first {
fmt.Print(" -> ")
}
first = false
fmt.Print("[")
for i, k := range node.keys {
if i > 0 {
fmt.Print(",")
}
fmt.Print(k)
}
fmt.Print("]")
node = node.next
}
fmt.Println()
}
// bpRangeQuery finds all keys in [lo, hi]
func bpRangeQuery(root *BPlusNode, lo, hi int) []int {
result := []int{}
if root == nil {
return result
}
node := findLeaf(root, lo)
for node != nil {
for _, k := range node.keys {
if k > hi {
return result
}
if k >= lo {
result = append(result, k)
}
}
node = node.next
}
return result
}
func main() {
var root *BPlusNode
// insert sequence
keys := []int{10, 20, 5, 15, 25, 30, 35, 40}
fmt.Print("Insert sequence: ")
for _, k := range keys {
fmt.Printf("%d ", k)
root = bpInsert(root, k)
}
fmt.Println()
fmt.Println()
// show leaf structure
fmt.Print("Leaf nodes (linked list): ")
bpPrintLeaves(root)
// traverse all keys
fmt.Print("Traversal: ")
for i, k := range bpTraverse(root) {
if i > 0 {
fmt.Print(" ")
}
fmt.Print(k)
}
fmt.Println()
// search
fmt.Printf("Search 15: %v\n", map[bool]string{true: "Found", false: "Not found"}[bpSearch(root, 15)])
fmt.Printf("Search 12: %v\n", map[bool]string{true: "Found", false: "Not found"}[bpSearch(root, 12)])
// range query
fmt.Print("Range query [15, 35]: ")
for i, k := range bpRangeQuery(root, 15, 35) {
if i > 0 {
fmt.Print(" ")
}
fmt.Print(k)
}
fmt.Println()
}
运行该程序将输出:
Insert sequence: 10 20 5 15 25 30 35 40
Leaf nodes (linked list): [5, 10] -> [15, 20] -> [25, 30] -> [35, 40]
Traversal: 5 10 15 20 25 30 35 40
Search 15: Found
Search 12: Not found
Range query [15, 35]: 15 20 25 30 35
四个语言版本的输出结果完全一致。分析输出结果:
- 叶子节点链表
[5,10] -> [15,20] -> [25,30] -> [35,40]展示了B+树的核心特征:所有数据存储在叶子节点中,叶子节点通过链表串联 - 遍历结果
5 10 15 20 25 30 35 40是所有关键字的升序排列,由叶子链表直接给出 - 搜索 15 成功找到(在叶子
[15,20]中),搜索 12 未找到(不存在) - 范围查询 [15, 35] 从叶子
[15,20]开始沿链表遍历,依次收集 15, 20, 25, 30, 35,遇到 40 > 35 停止
插入 10, 20, 5, 15, 25, 30, 35, 40(MAX=3)后,最终的树结构为:
[20]
/ \
[10] [30]
/ \ / \
[5,10]->[15,20]->[25,30]->[35,40]
- 根节点
[20]有 1 个关键字,2 个子节点 - 第二层
[10]和[30]是内部节点,仅存路由关键字 - 底层是 4 个叶子节点,通过链表
->串联 - 所有叶子节点都在同一深度,所有数据集中在叶子中
B+树的性质(总结)
B+树通过将数据集中在叶子节点并用链表串联,在B树的平衡性基础上增加了高效的范围查询能力,使其成为数据库和文件系统索引的标准选择。
时间复杂度
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 搜索(Search) | O(log n) | 必须走到叶子节点,路径长度固定为树高 |
| 插入(Insert) | O(log n) | 找到叶子 + 分裂传播,最多传播到根 |
| 删除(Delete) | O(log n) | 找到叶子 + 合并或借用,最多传播到根 |
| 范围查询(Range Query) | O(log n + k) | O(log n) 定位起点 + O(k) 沿链表遍历 k 个结果 |
| 遍历(Traversal) | O(n) | 沿叶子链表遍历全部关键字 |
B+树 vs B树 对比
| 特性 | B树 | B+树 |
|---|---|---|
| 数据位置 | 所有节点 | 仅叶子节点 |
| 内部节点内容 | 关键字 + 数据 | 仅路由关键字 |
| 叶子链接 | 无 | 链表串联 |
| 节点扇出 | 较小 | 较大(内部节点不存数据) |
| 树高 | 较高 | 较矮 |
| 单次查找 | 可提前终止 | 必须到叶子 |
| 范围查询 | O(n log n) 中序遍历 | O(log n + k) 链表遍历 |
| 顺序遍历 | 需递归中序 | 链表直接遍历 |
| 磁盘 I/O | 较多 | 较少(树更矮) |
磁盘访问优势
B+树比B树更适合磁盘存储的原因:
- 更大的扇出(Fan-out):内部节点不存储数据,同样大小的磁盘页可以容纳更多关键字,树更矮
- 更少的磁盘 I/O:树高更矮意味着查找路径更短,磁盘读取次数更少
- 稳定的时间复杂度:每次查找必须走到叶子,路径长度一致,性能可预测
- 高效的范围查询:链表结构使得范围扫描只需顺序读取,无需反复遍历树
实际中,当节点大小等于磁盘页大小(通常 4KB)时,一棵 B+树存储 10 亿条记录只需约 3~4 层,即 3~4 次磁盘读取即可完成查找。
实际应用
| 应用 | 说明 |
|---|---|
| MySQL InnoDB | 默认索引结构,主键索引和二级索引均使用 B+树 |
| PostgreSQL | 默认索引结构,支持多种索引类型,B+树最常用 |
| SQLite | 使用 B+树变体作为表和索引的存储结构 |
| NTFS | Windows 文件系统的目录索引 |
| ext4 | Linux 文件系统的 Htree 目录索引 |
| Oracle Database | B-tree 索引的默认实现本质上是 B+树 |

浙公网安备 33010602011771号