B树算法Java实现:数据结构解析与完整代码

半杯拿铁
半杯拿铁
发布时间:2026-10-13 | 5人浏览过
原创

本文详细解析B树的定义、引入原因及核心性质,并提供Java语言的完整实现代码。内容涵盖从基本结构到插入分裂、递归查找等关键操作的代码级演示,适合需要理解数据库索引底层或磁盘存储结构的开发者。

B树的核心特性与引入背景

在计算机科学中,B树是一种自平衡的多路查找树,能够保持数据有序。它的核心优势在于让查找数据、顺序访问、插入数据及删除的动作都在对数时间内完成。

为什么要引入B树?这主要源于数据存储介质的差异:

  • 内存 vs 磁盘:红黑树等平衡树通常被视为“内部查找树”,适用于存入内存的数据。而B树是这些算法的扩展,专门用于保存在磁盘或者网络上的符号表进行外部查找。
  • I/O优化:文件可能非常大,难以全部载入内存。如果树的深度过大,会导致频繁的磁盘I/O读写,进而降低查询效率。因此,降低树的深度至关重要,B树作为多路查找树应运而生。

B树的定义与节点结构

B树具有以下显著特点(设最大孩子数为 M,M > 2):

  1. 每个结点最多含有 M 个孩子。
  2. 除根结点和叶子结点外,其它每个结点至少有 [ceil(M / 2)] 个孩子。
  3. 若根结点不是叶子结点,则至少有2个孩子(特殊情况即整棵树只有一个根节点)。
  4. 所有叶子结点都出现在同一层,它们是外部结点,保存实际内容(key和value)。

B树的操作逻辑:查找与插入

B树的操作逻辑通过区分内部结点(索引)和外部结点(内容)来简化。为了实现这一点,代码中常引入一个特殊的哨兵键(用 * 表示),它小于其他所有键,以此约定简化边界处理。

初始化时,B树只含有一个根结点,该根结点仅含有哨兵键。随着数据的插入,树的高度会动态增长,并通过分裂机制维持平衡。

Java实现完整代码

以下为基于Java语言的B树完整实现。代码引入了哨兵键,但在最终输出视图时会将其剔除以保持整洁。主要组件包括:

  • Node:节点类,存储子节点引用数组。
  • Entry:条目类,包含键值(外部结点)或子树指针(内部结点)。
  • put/get/search:负责插入、查找与递归搜索的逻辑。
  • split:当节点满员时执行分裂操作。
public class BTree<Key extends Comparable<Key>, Value> {
  // max children per B-tree node = M-1
  // (must be even and greater than 2)
  private static final int M = 4;

  private Node root;    // root of the B-tree
  private int height;   // height of the B-tree
  private int n;      // number of key-value pairs in the B-tree

  // helper B-tree node data type
  private static final class Node {
    private int m;               // number of children
    private Entry[] children = new Entry[M];  // the array of children

    // create a node with k children
    private Node(int k) {
      m = k;
    }
  }

  // internal nodes: only use key and next
  // external nodes: only use key and value
  private static class Entry {
    private Comparable key;
    private Object val;
    private Node next;   // helper field to iterate over array entries
    public Entry(Comparable key, Object val, Node next) {
      this.key = key;
      this.val = val;
      this.next = next;
    }
  }

  /**
   * Initializes an empty B-tree.
   */
  public BTree() {
    root = new Node(0);
  }

  /**
   * Returns true if this symbol table is empty.
   * @return {@code true} if this symbol table is empty; {@code false} otherwise
   */
  public boolean isEmpty() {
    return size() == 0;
  }

  /**
   * Returns the number of key-value pairs in this symbol table.
   * @return the number of key-value pairs in this symbol table
   */
  public int size() {
    return n;
  }

  /**
   * Returns the height of this B-tree (for debugging).
   *
   * @return the height of this B-tree
   */
  public int height() {
    return height;
  }

  /**
   * Returns the value associated with the given key.
   *
   * @param key the key
   * @return the value associated with the given key if the key is in the symbol table
   *     and {@code null} if the key is not in the symbol table
   * @throws NullPointerException if {@code key} is {@code null}
   */
  public Value get(Key key) {
    if (key == null) {
      throw new NullPointerException("key must not be null");
    }
    return search(root, key, height);
  }

  @SuppressWarnings("unchecked")
  private Value search(Node x, Key key, int ht) {
    Entry[] children = x.children;

    // external node到最底层叶子结点,遍历
    if (ht == 0) {
      for (int j = 0; j < x.m; j++)       
      {
        if (eq(key, children[j].key)) {
          return (Value) children[j].val;
        }
      }
    }

    // internal node递归查找next地址
    else {
      for (int j = 0; j < x.m; j++) {
        if (j+1 == x.m || less(key, children[j+1].key))
        {
          return search(children[j].next, key, ht-1);
        }
      }
    }
    return null;
  }

  /**
   * Inserts the key-value pair into the symbol table, overwriting the old value
   * with the new value if the key is already in the symbol table.
   * If the value is {@code null}, this effectively deletes the key from the symbol table.
   *
   * @param key the key
   * @param val the value
   * @throws NullPointerException if {@code key} is {@code null}
   */
  public void put(Key key, Value val) {
    if (key == null) {
      throw new NullPointerException("key must not be null");
    }
    Node u = insert(root, key, val, height); //分裂后生成的右结点
    n++;
    if (u == null) {
      return;
    }

    // need to split root重组root
    Node t = new Node(2);
    t.children[0] = new Entry(root.children[0].key, null, root);
    t.children[1] = new Entry(u.children[0].key, null, u);
    root = t;
    height++;
  }

  private Node insert(Node h, Key key, Value val, int ht) {
    int j;
    Entry t = new Entry(key, val, null);

    // external node外部结点,也是叶子结点,在树的最底层,存的是内容value
    if (ht == 0) {
      for (j = 0; j < h.m; j++) {
        if (less(key, h.children[j].key)) {
          break;
        }
      }
    }

    // internal node内部结点,存的是next地址
    else {
      for (j = 0; j < h.m; j++) {
        if ((j+1 == h.m) || less(key, h.children[j+1].key)) {
          Node u = insert(h.children[j++].next, key, val, ht-1);
          if (u == null) {
            return null;
          }
          t.key = u.children[0].key;
          t.next = u;
          break;
        }
      }
    }

    for (int i = h.m; i > j; i--)
    {
      h.children[i] = h.children[i-1];
    }
    h.children[j] = t;
    h.m++;
    if (h.m < M) {
      return null;
    }
    else     
    {  //分裂结点
      return split(h);
    }
  }

  // split node in half
  private Node split(Node h) {
    Node t = new Node(M/2);
    h.m = M/2;
    for (int j = 0; j < M/2; j++)
    {
      t.children[j] = h.children[M/2+j];     
    }
    return t;  
  }

  /**
   * Returns a string representation of this B-tree (for debugging).
   *
   * @return a string representation of this B-tree.
   */
  public String toString() {
    return toString(root, height, "") + "\n";
  }

  private String toString(Node h, int ht, String indent) {
    StringBuilder s = new StringBuilder();
    Entry[] children = h.children;

    if (ht == 0) {
      for (int j = 0; j < h.m; j++) {
        s.append(indent + children[j].key + " " + children[j].val + "\n");
      }
    } else {
      for (int j = 0; j < h.m; j++) {
        if (j > 0) {
          s.append(indent + "(" + children[j].key + ")\n");
        }
        s.append(toString(children[j].next, ht-1, indent + "   "));
      }
    }
    return s.toString();
  }

  // comparison functions - make Comparable instead of Key to avoid casts
  private boolean less(Comparable k1, Comparable k2) {
    return k1.compareTo(k2) < 0;
  }

  private boolean eq(Comparable k1, Comparable k2) {
    return k1.compareTo(k2) == 0;
  }

  /**
   * Unit tests the {@code BTree} data type.
   *
   * @param args the command-line arguments
   */
  public static void main(String[] args) {
    BTree<String, String> st = new BTree<String, String>();

    st.put("www.cs.princeton.edu", "128.112.136.12");
    st.put("www.cs.princeton.edu", "128.112.136.11");
    st.put("www.princeton.edu",  "128.112.128.15");
    st.put("www.yale.edu",     "130.132.143.21");
    st.put("www.simpsons.com",   "209.052.165.60");
    st.put("www.apple.com",    "17.112.152.32");
    st.put("www.amazon.com",    "207.171.182.16");
    st.put("www.ebay.com",     "66.135.192.87");
    st.put("www.cnn.com",     "64.236.16.20");
    st.put("www.google.com",    "216.239.41.99");
    st.put("www.nytimes.com",   "199.239.136.200");
    st.put("www.microsoft.com",  "207.126.99.140");
    st.put("www.dell.com",     "143.166.224.230");
    st.put("www.slashdot.org",   "66.35.250.151");
    st.put("www.espn.com",     "199.181.135.201");
    st.put("www.weather.com",   "63.111.66.11");
    st.put("www.yahoo.com",    "216.109.118.65");


    System.out.println("cs.princeton.edu: " + st.get("www.cs.princeton.edu"));
    System.out.println("hardvardsucks.com: " + st.get("www.harvardsucks.com"));
    System.out.println("simpsons.com:   " + st.get("www.simpsons.com"));
    System.out.println("apple.com:     " + st.get("www.apple.com"));
    System.out.println("ebay.com:     " + st.get("www.ebay.com"));
    System.out.println("dell.com:     " + st.get("www.dell.com"));
    System.out.println();

    System.out.println("size:  " + st.size());
    System.out.println("height: " + st.height());
    System.out.println(st);
    System.out.println();
  }
}

运行结果与分析

上述代码通过单元测试输入了多个域名及其对应的IP地址。由于引入了哨兵键,内部打印的树结构会包含额外的键,但实际的数据获取不受影响。

基础查询结果

cs.princeton.edu:  128.112.136.12
hardvardsucks.com: null
simpsons.com:      209.052.165.60
apple.com:         17.112.152.32
ebpay.com:         66.135.192.87
dell.com:          143.166.224.230

size:             17
height:           2

可以看到,树的高度仅为2,这说明B树在多路合并下保持了极低的深度,即便插入了大量数据,也能保证高效的查找性能。以上就是B树算法Java实现的详细内容,更多关于Java算法实现的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表