2026-05-10 13:12:44 +08:00

286 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// grid-db/src/index/btree.js
// Grid-DB · B+Tree 主键索引
// 有序键值索引,支持范围扫描
// PRJ-GDB-001 · Phase 1 · ZY-GDB-P1-001
// 版权:国作登字-2026-A-00037559
'use strict';
/**
* B+Tree — 主键索引
*
* 本体论锚定:索引 = 纸的目录。
* 目录按页码排列,可以快速定位到任意一页。
* 叶子节点串联 = 目录页之间的连续翻阅。
*
* 所有数据存储在叶子节点,内部节点仅存储路由键。
* 叶子节点之间通过 next 指针串联,支持高效范围扫描。
*/
class BTreeNode {
/**
* @param {boolean} isLeaf 是否为叶子节点
*/
constructor(isLeaf = false) {
this.isLeaf = isLeaf;
this.keys = [];
this.children = []; // 内部节点:子节点引用;叶子节点:对应值
this.next = null; // 叶子节点链表指针
}
}
class BTree {
/**
* @param {number} [order] B+Tree 的阶数(每个节点最大子节点数),默认 32
*/
constructor(order = 32) {
if (typeof order !== 'number' || order < 3) {
throw new Error('BTree: order 必须是 >= 3 的整数');
}
this._order = order;
this._maxKeys = order - 1;
this._minKeys = Math.ceil(order / 2) - 1;
this._root = new BTreeNode(true);
this._size = 0;
}
/**
* 当前索引中的条目数量
* @returns {number}
*/
get size() {
return this._size;
}
/**
* 插入键值对
* @param {string} key 键
* @param {*} value 值
*/
insert(key, value) {
if (typeof key !== 'string') {
throw new Error('BTree.insert: key 必须是字符串');
}
// 检查是否已存在(更新)
const leaf = this._findLeaf(this._root, key);
const idx = leaf.keys.indexOf(key);
if (idx !== -1) {
leaf.children[idx] = value;
return;
}
// 根节点已满,先分裂
if (this._root.keys.length >= this._maxKeys) {
const newRoot = new BTreeNode(false);
newRoot.children.push(this._root);
this._splitChild(newRoot, 0);
this._root = newRoot;
}
this._insertNonFull(this._root, key, value);
this._size++;
}
/**
* 查找键对应的值
* @param {string} key
* @returns {*|undefined} 找到返回值,否则 undefined
*/
find(key) {
const leaf = this._findLeaf(this._root, key);
const idx = leaf.keys.indexOf(key);
if (idx !== -1) {
return leaf.children[idx];
}
return undefined;
}
/**
* 删除键
* @param {string} key
* @returns {boolean} 是否删除成功
*/
delete(key) {
const leaf = this._findLeaf(this._root, key);
const idx = leaf.keys.indexOf(key);
if (idx === -1) {
return false;
}
leaf.keys.splice(idx, 1);
leaf.children.splice(idx, 1);
this._size--;
return true;
}
/**
* 范围查询(闭区间 [startKey, endKey]
*
* 利用叶子节点链表高效遍历
*
* @param {string} startKey 起始键
* @param {string} endKey 结束键
* @returns {Array<{key: string, value: *}>} 有序结果
*/
range(startKey, endKey) {
const results = [];
let leaf = this._findLeaf(this._root, startKey);
while (leaf) {
for (let i = 0; i < leaf.keys.length; i++) {
if (leaf.keys[i] >= startKey && leaf.keys[i] <= endKey) {
results.push({ key: leaf.keys[i], value: leaf.children[i] });
}
if (leaf.keys[i] > endKey) {
return results;
}
}
leaf = leaf.next;
}
return results;
}
/**
* 序列化为 JSON
* @returns {object}
*/
toJSON() {
const entries = [];
let leaf = this._getFirstLeaf();
while (leaf) {
for (let i = 0; i < leaf.keys.length; i++) {
entries.push({ key: leaf.keys[i], value: leaf.children[i] });
}
leaf = leaf.next;
}
return { order: this._order, entries };
}
/**
* 从 JSON 还原 B+Tree
* @param {object} json
* @returns {BTree}
*/
static fromJSON(json) {
const tree = new BTree(json.order || 32);
for (const entry of json.entries) {
tree.insert(entry.key, entry.value);
}
return tree;
}
/**
* 清空索引
*/
clear() {
this._root = new BTreeNode(true);
this._size = 0;
}
// ── 内部方法 ──
/**
* 找到 key 应当所在的叶子节点
* @param {BTreeNode} node
* @param {string} key
* @returns {BTreeNode}
*/
_findLeaf(node, key) {
if (node.isLeaf) {
return node;
}
// 在内部节点中找到正确的子节点
let i = 0;
while (i < node.keys.length && key >= node.keys[i]) {
i++;
}
return this._findLeaf(node.children[i], key);
}
/**
* 获取最左叶子节点
* @returns {BTreeNode}
*/
_getFirstLeaf() {
let node = this._root;
while (!node.isLeaf) {
node = node.children[0];
}
return node;
}
/**
* 向非满节点插入
* @param {BTreeNode} node
* @param {string} key
* @param {*} value
*/
_insertNonFull(node, key, value) {
if (node.isLeaf) {
// 在叶子节点中找到插入位置(保持有序)
let i = 0;
while (i < node.keys.length && node.keys[i] < key) {
i++;
}
node.keys.splice(i, 0, key);
node.children.splice(i, 0, value);
return;
}
// 内部节点:找到正确的子节点
let i = 0;
while (i < node.keys.length && key >= node.keys[i]) {
i++;
}
// 如果目标子节点已满,先分裂
if (node.children[i].keys.length >= this._maxKeys) {
this._splitChild(node, i);
if (key >= node.keys[i]) {
i++;
}
}
this._insertNonFull(node.children[i], key, value);
}
/**
* 分裂子节点
* @param {BTreeNode} parent 父节点
* @param {number} childIndex 要分裂的子节点索引
*/
_splitChild(parent, childIndex) {
const child = parent.children[childIndex];
const mid = Math.floor(child.keys.length / 2);
const newNode = new BTreeNode(child.isLeaf);
if (child.isLeaf) {
// 叶子节点分裂splice 从 mid 处截断 child返回值作为 newNode 内容
newNode.keys = child.keys.splice(mid);
newNode.children = child.children.splice(mid);
// 维护叶子链表
newNode.next = child.next;
child.next = newNode;
// 提升第一个新键到父节点
parent.keys.splice(childIndex, 0, newNode.keys[0]);
parent.children.splice(childIndex + 1, 0, newNode);
} else {
// 内部节点分裂:中间键提升,不保留在子节点
const midKey = child.keys[mid];
newNode.keys = child.keys.splice(mid + 1);
newNode.children = child.children.splice(mid + 1);
child.keys.splice(mid); // 移除中间键
parent.keys.splice(childIndex, 0, midKey);
parent.children.splice(childIndex + 1, 0, newNode);
}
}
}
module.exports = BTree;