跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava算法

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

综述由AI生成B-树是一种平衡的多路查找树,通过减少树高降低磁盘 I/O。详细解析了 B-树的定义、特性及插入删除逻辑,并提供了完整的 Java 代码实现,涵盖节点分裂、父指针维护等关键细节。同时对比了 B+ 树与 B* 树的区别,适用于数据库索引及文件系统场景。

PentesterX发布于 2026/3/15更新于 2026/6/1121 浏览
B-树原理与 Java 模拟实现详解

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

B-树是一种平衡的 M 路(M>=2)查找树,其核心设计目标是减少磁盘 I/O 次数。相比二叉搜索树,B-树通过增加每个节点的子节点数量来降低树的高度,从而在海量数据存储场景中表现优异。

定义与特性

  1. 根节点至少有两个孩子;
  2. 每个非根节点至少有 M/2(上取整) 个关键字,至多有 M-1 个关键字,且升序排列;
  3. 每个非根节点至少有 M/2(上取整) 个孩子,至多有 M 个孩子;
  4. key[i] 和 key[i+1] 之间的孩子节点的值介于两者之间;
  5. 所有叶子节点位于同一层。

插入分析

以 M=3 为例,每个节点最多存储两个数据。为了简化分裂逻辑,我们允许节点暂时容纳三个关键字,插入第三个后再触发分裂。

下面以序列【53, 139, 75, 49, 145, 36, 101】构建 B 树的过程演示:

插入 53

插入 139

插入 75

引发分裂

插入总结:

  1. 若树为空,直接创建根节点。
  2. 若非空,定位到叶子节点位置。
  3. 检查元素是否已存在,避免重复插入。
  4. 按排序插入后,若节点满(达到 M),则执行分裂操作。
  5. 分裂可能向上传播,直至根节点或不再需要分裂。

模拟实现

基本结构

我们需要一个节点类 BTRNode,包含关键字数组、子节点指针、父节点指针以及当前有效关键字数量。同时需要一个辅助类 Pair 来返回查找结果。

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; }
}

public class MyBtree {
    static class BTRNode {
        public int[] keys;      // 关键字
        public BTRNode[] subs;  // 孩子节点
        public BTRNode parent;  // 父节点
        public int usedSize;    // 当前关键字数量

        public BTRNode() {
            // 预留空间,方便后续分裂操作
            this.keys = new int[M];
            this.subs = new BTRNode[M + 1];
        }
    }

    public static final int M = 3;
    public BTRNode root;
}

寻找插入位置

这里返回 Pair<BTRNode, Integer> 很有必要。因为我们需要区分两种情况:一是找到了相同的 Key(不插入),二是找到了插入位置(叶子节点)。仅返回节点无法区分是否存在该 Key,仅返回下标无法知道插入点。

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];
    }
    return new Pair<>(parent, -1);
}

插入与分裂

插入逻辑相对直观:找到叶子节点后插入,若节点满则分裂。分裂时需要将中间元素提至父节点,右侧元素移至新节点,并更新父子指针关系。

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;
    }
}

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);
    }
}

中序遍历

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]);
}

删除操作

删除逻辑比插入复杂,主要涉及借位和合并。

  1. 若待删 Key 在非叶子节点,用后继 Key 覆盖,转为删除叶子节点的后继 Key。
  2. 若删除后节点 Key 数不足下限(ceil(M/2)-1),尝试从兄弟节点借位。
  3. 若兄弟节点也无多余 Key,则与兄弟节点及父节点 Key 合并。

B+ 树与 B* 树

B+ 树是 B-树的变体,所有关键字都存储在叶子节点,并通过链表连接。这使得范围查询效率更高,更适合文件系统和数据库索引。

*B 树**进一步提高了节点利用率,非叶子节点的关键字使用率至少为 2/3,减少了分裂频率。

总结

B-树及其变种是现代数据存储系统的基石。理解其插入分裂机制和内存布局,对于优化数据库性能至关重要。上述 Java 实现展示了核心逻辑,实际工程中还需考虑并发控制与持久化策略。

目录

  1. B-树原理与 Java 模拟实现详解
  2. 定义与特性
  3. 插入分析
  4. 模拟实现
  5. 基本结构
  6. 寻找插入位置
  7. 插入与分裂
  8. 中序遍历
  9. 删除操作
  10. B+ 树与 B* 树
  11. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Java 核心面试指南:基础、并发与集合实战
  • Java 顺序表实现杨辉三角思路与代码
  • 渗透测试全流程解析与实战方法
  • 面试高频缓存算法:LRU 与 LFU 原理及 Java 实现
  • 分治算法详解:快速排序、归并排序与快速选择
  • ChatGPT Go 在亚洲 16 国上线及三大使用限制
  • 10 款主流网络爬虫工具深度对比与选型指南
  • 强化学习基础:动态规划算法原理与实现
  • 用 Vibe Coding 实现 AI 全自动代码生成与交付
  • SKResNet 架构详解:融合选择性卷积与残差结构
  • Llama-Factory 模型服务负载均衡实战配置
  • OpenCode 接入 Kimi K2.5 配置与实战指南
  • C++ 轻量级搜索引擎实战:正/倒排索引构建详解
  • 动态规划路径类 DP 入门:3 道经典例题详解
  • C++ 算法实战:排序子序列划分与 LIS 优化
  • Python 列表与元组:核心区别与常用操作详解
  • HarmonyOS Next DevEco Studio 端云一体化开发业务介绍
  • 基于大语言模型和 RAG 的知识库问答系统
  • 大语言模型人类对齐五大核心算法:SFT、RLHF、DPO、PPO、GRPO
  • next-ai-draw-io:基于 Next.js 的 AI 绘图工具搭建指南

相关免费在线工具

  • 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