跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
Javajava算法

B-树原理与 Java 模拟实现详解

B-树作为平衡多路查找树,通过减少树高降低磁盘 I/O。详细解析其定义、插入分裂机制及 Java 模拟实现,涵盖节点结构、查找定位、分裂逻辑与中序遍历。同时对比 B+ 树与 B* 树特性,探讨其在数据库索引与文件系统中的应用场景,适合希望深入理解底层数据结构与工程落地的开发者阅读。

BackendPro发布于 2026/3/22更新于 2026/5/76 浏览
B-树原理与 Java 模拟实现详解

B-树基础概念

B-树是一种平衡的多路查找树,通常用于数据库和文件系统的索引结构。它通过增加每个节点的关键字数量来减少树的高度,从而显著降低磁盘 I/O 次数。

核心特性

  1. 根节点:至少有两个孩子(除非它是叶子节点)。
  2. 非根节点:至少有 ceil(M/2) - 1 个关键字,至多有 M - 1 个关键字,且按升序排列。
  3. 子节点数:每个非根节点至少有 ceil(M/2) 个孩子,至多有 M 个孩子。
  4. 区间约束:关键字 key[i] 和 key[i+1] 之间的子树值介于两者之间。
  5. 平衡性:所有叶子节点都在同一层,通过分裂和合并保持平衡。

插入操作分析

以 M=3 的 B-树为例,每个节点最多存 2 个关键字。为了方便理解,我们允许节点在插入第三个关键字时暂时存储,随后立即触发分裂。

插入流程

假设我们要插入序列 [53, 139, 75, 49, 145, 36, 101]。

  1. 初始插入:若树为空,直接创建根节点。
  2. 查找位置:从根节点向下遍历,找到合适的叶子节点位置。注意,B-树的插入一定发生在叶子节点。
  3. 插入元素:在找到的节点中按顺序插入新关键字。
  4. 检查溢出:如果节点关键字数量达到上限(M),则必须分裂。

文章配图

当节点满员时,我们需要将中间关键字提升到父节点,剩余部分分裂成两个新节点。如果父节点也满了,这个过程会递归向上,直到根节点分裂或不再需要分裂。

文章配图

Java 模拟实现

下面给出完整的 Java 代码实现。为了清晰展示逻辑,我们将辅助类 Pair 和主类 MyBtree 分开定义。

数据结构定义

public class Pair<K,V> {
    private K key;
    private V val;
    
    public Pair(K key, V val) {
        this.key = key;
        this.val = val;
    }
    
    public K getKey() { return key; }
    public void setKey(K key) { this.key = key; }
    public V getVal() { return val; }
    public void setVal(V val) { this.val = val; }
}

核心逻辑

1. 查找插入位置

为什么返回 Pair<BTRNode, Integer>? 因为我们需要区分两种情况:一是找到了已存在的 Key,二是找到了插入位置。仅返回节点无法区分,仅返回下标无法定位节点。使用 Pair 可以一次性获取节点引用和在该节点中的索引。

private Pair<BTRNode, Integer> find(int key) {
    BTRNode cur = root;
    BTRNode parent = null;
    while (cur != null) {
        int i = 0;
        while (i < cur.usedSize) {
            if (cur.keys[i] == key) {
                // 存在该元素,返回节点及下标
                return new Pair<>(cur, i);
            } else if (cur.keys[i] < key) {
                i++;
            } else {
                break;
            }
        }
        parent = cur;
        cur = cur.subs[i];
    }
    // 未找到,返回父节点及 -1 表示插入位置
    return new Pair<>(parent, -1);
}
2. 插入与分裂

插入时先判断是否重复,再寻找位置。如果节点未满直接插入;如果满了,调用 split 方法。

public boolean insert(int key) {
    // 空树处理
    if (root == null) {
        root = new BTRNode();
        root.keys[0] = key;
        root.usedSize++;
        return true;
    }

    // 查找是否存在
    Pair<BTRNode, Integer> pair = find(key);
    if (pair.getVal() != -1) {
        return false; // 已存在
    }

    // 插入到叶子节点
    BTRNode parent = pair.getKey();
    int index = parent.usedSize - 1;
    for (; index >= 0; index--) {
        if (parent.keys[index] >= key) {
            parent.keys[index + 1] = parent.keys[index];
        } else {
            break;
        }
    }
    parent.keys[index + 1] = key;
    parent.usedSize++;

    // 检查是否需要分裂
    if (parent.usedSize < M) {
        return true;
    } else {
        split(parent);
        return true;
    }
}
3. 节点分裂

这是最复杂的部分。需要将当前节点的中间元素上提,右侧元素和新节点分离,并更新父指针。

private void split(BTRNode cur) {
    BTRNode newNode = new BTRNode();
    BTRNode parent = cur.parent;
    
    // 确定中间位置
    int mid = cur.usedSize >> 1;
    int i = mid + 1;
    int j = 0;
    
    // 移动数据和子节点到新节点
    for (; i < cur.usedSize; i++) {
        newNode.keys[j] = cur.keys[i];
        newNode.subs[j] = cur.subs[i];
        if (newNode.subs[j] != null) {
            newNode.subs[j].parent = newNode;
        }
        j++;
    }
    // 多拷贝一次子节点指针
    newNode.subs[j] = cur.subs[i];
    if (newNode.subs[j] != null) {
        newNode.subs[j].parent = newNode;
    }
    
    newNode.usedSize = j;
    cur.usedSize = cur.usedSize - j - 1;
    
    // 特殊处理根节点分裂
    if (cur == root) {
        root = new BTRNode();
        root.keys[0] = cur.keys[mid];
        root.subs[0] = cur;
        root.subs[1] = newNode;
        root.usedSize = 1;
        cur.parent = root;
        newNode.parent = root;
        return;
    }
    
    // 更新父节点
    newNode.parent = parent;
    int endT = parent.usedSize - 1;
    int midVal = cur.keys[mid];
    
    for (; endT >= 0; endT--) {
        if (parent.keys[endT] >= midVal) {
            parent.keys[endT + 1] = parent.keys[endT];
            parent.subs[endT + 2] = parent.subs[endT + 1];
        } else {
            break;
        }
    }
    parent.keys[endT + 1] = midVal;
    parent.subs[endT + 2] = newNode;
    parent.usedSize++;
    
    // 递归检查父节点是否也需要分裂
    if (parent.usedSize >= M) {
        split(parent);
    }
}
4. 中序遍历

验证数据有序性的关键方法。

private void inorder(BTRNode root) {
    if (root == null) return;
    for (int i = 0; i < root.usedSize; ++i) {
        inorder(root.subs[i]);
        System.out.println(root.keys[i]);
    }
    inorder(root.subs[root.usedSize]);
}

完整测试

public static void main(String[] args) {
    MyBtree myBtree = new MyBtree();
    int[] array = {53, 139, 75, 49, 145, 36, 101};
    for (int i = 0; i < array.length; i++) {
        myBtree.insert(array[i]);
    }
    myBtree.inorder(myBtree.root);
}

运行结果将输出有序序列,证明插入逻辑正确。

文章配图

B-树的删除

删除操作相对复杂,主要涉及借位和合并。

  1. 非叶子节点删除:用后继关键字覆盖待删关键字,然后在后继所在子树中递归删除。
  2. 叶子节点不足:如果删除后节点关键字少于下限,尝试向兄弟借位。
  3. 合并:如果兄弟也无多余关键字,则将父节点关键字下移,与当前节点及兄弟节点合并。

文章配图

变种对比:B+ 树与 B* 树

B+ 树

B+ 树是 B-树的改进版,广泛用于数据库索引。

  • 优势:所有数据都在叶子节点,形成有序链表,适合范围查询;非叶子节点只存索引,能容纳更多键值,树更矮。
  • 场景:MySQL、PostgreSQL 等关系型数据库。

文章配图

B* 树

B* 树进一步提高了空间利用率。

  • 特点:非叶子节点关键字下限提高至 2/3M,分裂时尽量分配给兄弟而非新建节点。
  • 优势:更高的空间利用率和更好的平衡性。

总结

B-树通过平衡多路搜索有效解决了大规模数据的存储与检索问题。其核心在于通过控制节点大小来平衡树高,减少磁盘 I/O。在实际工程中,虽然 B+ 树更为常见,但理解 B-树的分裂与合并机制是掌握底层索引技术的基石。

目录

  1. B-树基础概念
  2. 核心特性
  3. 插入操作分析
  4. 插入流程
  5. Java 模拟实现
  6. 数据结构定义
  7. 核心逻辑
  8. 1. 查找插入位置
  9. 2. 插入与分裂
  10. 3. 节点分裂
  11. 4. 中序遍历
  12. 完整测试
  13. B-树的删除
  14. 变种对比:B+ 树与 B* 树
  15. B+ 树
  16. B* 树
  17. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Harness Engineering:AI 时代的工程最佳实践
  • 2025 年开源图生图模型盘点:Kandinsky 5.0、Qwen-Image 与 Z-Image
  • 算法基础:数论
  • HarmonyOS 6 Navigation 组件导航生命周期解析
  • LFM2.5-1.2B-Thinking 打造个人 AI 写作助手指南
  • 基于 SpringBoot2+Vue3 的旅游出行指南系统设计与实现
  • 前端正则表达式实战指南:语法、API 与高频场景避坑
  • 流处理、实时分析与 RAG 驱动的 Python ETL 框架:构建智能数据管道 (上)
  • Claude Code 本地化接入魔搭社区指南
  • TCP 拥塞控制算法详解:CUBIC、BBR 与传统演进
  • Google GenAI Toolbox:企业级 AI 数据库中间件与 LLM-SQL 安全互联实践
  • Python 列表内存存储本质:差异原因与优化建议
  • MarianCG:受机器翻译启发的代码生成 Transformer 模型
  • Qwen3-VL 结合 LLaMA-Factory 进行 Grounding 任务 LoRA 微调
  • Tauri 架构解析:从 WebView 到工具链与生态
  • AI 写论文提示词指令大全
  • LeetCode 868. 二进制间距
  • GitNexus 代码库知识图谱技术方案分析
  • Android MVVM 架构核心组件:Lifecycle 详解与应用
  • STM32单片机智能宠物喂养系统设计

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online