286 lines
6.8 KiB
JavaScript
286 lines
6.8 KiB
JavaScript
// 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;
|