B+树我花了很久时间才实现。具体说来难处在于资料都讲得不详细,跳过了很多细节。所以我很久没更新博文了。B+树不仅仅是对B-树的扩展。B+树在B-树的基础上,增加了以下规则:
1. 数据只保存在叶子上;
2. 所有的叶子通过连接起来,形成链表。
3. 叶子和中间节点保存的数据数量是不一致的。中间节点保存的key数目是m-1,叶子节点保存的数据是l,l和m是毫不相干的变量。因为每个节点对应磁盘的一个单元,只存索引的中间节点和存数据的叶子节点数据块长度肯定不一样。所以m值和索引的长度相关,l值和数据的长度相关。毫无疑问,l值大部分情况下都会小于m值。
4. 因为数据只保存在叶子上,所以key和叶子上的数据一定会有重复。重复的条件是右边的孩子可以等于索引键值。
一棵典型的B-树如下图:
需要注意的是实际项目不会直接使用B+树,而是使用B+树的三种变体:B-link树,B*树和sx-latch B+树。但是都是以B+树为基础的。B+树的算法和B-树最大的不同是B+树无论删除还是插入,都是先从root查找到叶子,操作数据时从叶子开始,向上递归,而B-树是从root开始向下递归,变查找边操作数据。
插入过程
B+树的插入过程还是比较简单的。但是和B-树不同。找到叶子,如果叶子没满,就直接插入数据,如果叶子满了。那就先创建一个新节点,然后把50%的数据移动到新叶子,当然B-树的分裂也是移动50%的数据,这一点很相似。B+树的中间节点只能左移右移,不能进行迁移50%数据这样的操作。但是不同的是递归方向,另外的不同点是因为B+树所有数据存在叶子上,所以新分裂出来的索引,是右边子树的最小值。
我给个表格:
场景 | 处理方法 |
---|---|
叶子未满 | 直接插入 |
叶子溢出 | 创建新叶子,放在右边,将原叶子一半的数据传输过来,插入父节点中,key插入新节点中, 右边叶子的第一个元素作为父key(这个操作会导致下面的两种溢出) |
中间节点溢出 | 创建空节点,把左边最大的子元素(假设为max-child)移动到新节点,key放到新节点中。向下找到max-child的最小元素,插入到父节点 |
root溢出 | 和中间节点一样,不同的是将max-child的最小元素作为新root的唯一元素,并且链接好原root和新节点 |
给几张图吧,首先是叶子的分裂50%的图。
下面这张图是向上递归分裂的过程。前面说了叶子节点迁移50%,中间节点只能分出一个key,如果是root则设置新的root,增加树的高度,下面这两张图,把三种场景都囊括了。
删除过程
删除过程是非常复杂的。我也是卡在这里。我卡住的原因是按照B-树的思想,从上往下处理,结果进入了一个死局,改好了这里,坏了那里。后来详细阅读各个资料,茶饭不思,终于搞明白了删除的正确方法,删除的正确方法是,先定位到叶子,在叶子上删除。
我的代码为了简化,是l=m-1的。但是思想一样,删除过程具体的算法是这样的:
1. 定位到叶子;
2. 如果叶子就是root,直接删除,返回,结束。
3. 如果叶子没有下溢(元素数量等于
⌈
l
2
⌉
−
1
\lceil \frac l2\rceil-1
⌈2l⌉−1,删除后元素数量不满足B+树条件,式子中的符号是向上取整的意思)并且要删除的不是叶子的最小元素,那么直接删除,返回,结束。
4. 如果没有下溢,但是key是叶子的最小元素,那么先删除自己,再向上循环用自己的兄弟替换和自己相同的元素。
5. 如果下溢,则向左借一个元素、向右借一个元素或者合并叶子。
给几张图,向左借叶子元素,需要注意的是需要换掉父节点元素:
向右借叶子元素:
合并叶子,这个时候会删除父节点索引元素,与B-树不同的是,B+树会存在父元素与子元素相同的情况,这种情况下合并后不是满的:
在合并叶子后,会删除父节点元素。删除非叶子元素时,是先判断是否会下溢,如果会,那么先调整自己,再删除。父元素的左移右移和合并代码类似。我就不再给图了。
合并后如果会造成root只有一个子节点,那么直接将root指向这个唯一的子节点,因为原root没有保存数据,只是保存了索引,所以可以直接丢弃。
java代码
我这里的Java代码并不全,篇幅有限,没有贴父类代码。具体可以从我的git中下载,地址为:https://e.coding.net/buildt/data-structure/trees.git 。
BPlusTree的代码如下:
package com.youngthing.trees.bplus;
import com.youngthing.trees.b.BTree;
import com.youngthing.trees.b.SortedMultiwayNode;
import java.util.logging.Logger;
public class BPlusTree<T extends Comparable<T>> extends BTree<T> {
private final BPlusNode head;
private final BPlusNode tail;
private final int m;
Logger bplusLogger = Logger.getLogger("bplus.split");
public BPlusTree(int m) {
super(m);
super.root = new BPlusNode<>(m);
head = tail = (BPlusNode) root;
this.m = m;
}
@Override
public void add(T t) {
BPlusNode<T> r = (BPlusNode<T>) this.root;
if (r.getValue().isEmpty()) {
r.insert(t);
} else {
BPlusNode<T> leaf = (BPlusNode<T>) ((SortedMultiwayNode<T>) root).findToInsert(t);
insert(t, leaf);
}
}
@Override
public BPlusNode<T> getRoot() {
return (BPlusNode<T>) super.getRoot();
}
@Override
public void delete(T key) {
if (root.isLeaf()) {
final BPlusNode<T> node = (BPlusNode<T>) root;
node.removeKey(node.binarySearch(key)[0]);
return;
}
// 第一步是定位到叶子
final BPlusNode<T> leafNode = (BPlusNode<T>) getRoot().findLeafNode(key);
// 满足条件就直接删除
int t = m / 2;
final int[] ints = leafNode.binarySearch(key);
// 节点最小为t-1
if (leafNode.length >= t && !leafNode.values[0].equals(key)) {
// 直接删除
leafNode.removeKeyWithoutBranches(ints[0]);
} else {
// 删除自己
// 再删除父节点
leafNode.removeLeaf(t, ints);
}
if (root.length == 0 && !root.isLeaf()) {
root = root.branches(0);
root.setParent(null);
}
}
private static class InsertResult<X extends Comparable<X>> {
private final int index;
private final BPlusNode<X> node;
public InsertResult(int index, BPlusNode<X> node) {
this.index = index;
this.node = node;
}
}
private InsertResult<T> insert(T t, BPlusNode<T> node) {
if (node.isFull()) {
return splitAndInsert(t, node);
} else {
return new InsertResult(node.insert(t), node);
}
}
private InsertResult splitAndInsert(T t, BPlusNode<T> node) {
bplusLogger.info("before split" + node.hashCode());
bplusLogger.info(root.toGraphString());
// 那就分裂啊
final BPlusNode<T> newNode = new BPlusNode<>(m);
// transer
if (node.isLeaf()) {
node.transfer(newNode);
} else {
newNode.initBranches();
newNode.branches(0, node.branches(m - 1));
node.length--;
}
final InsertResult<T> insertResult = insert(t, newNode);
final int index = insertResult.index;
if (node.isLeaf()) {
node.setNext(newNode);
}
if (node == this.root) {
final BPlusNode<T> newRoot = new BPlusNode<>(m);
// 这里加个判断
// 这里有个bug
if (this.root.isLeaf()) {
insert((T) newNode.values[0], newRoot);
} else {
insert((T) this.root.values[root.values.length - 1], newRoot);
}
newRoot.initBranches();
newRoot.branches(0, this.root);
newRoot.branches(1, newNode);
this.root = newRoot;
} else {
// 这里错了
BPlusNode<T> min = newNode;
while (!min.isLeaf()) {
min = (BPlusNode<T>) min.branches(0);
}
final InsertResult<T> insert = insert(min.getValue().get(0), (BPlusNode<T>) node.getParent());
// 插入之后还要右移所有的key啊
// 如果parent分裂之后,这个就不对了
insert.node.initBranches();
insert.node.branches(insert.index + 1, newNode);
}
bplusLogger.info("after split" + node.hashCode());
bplusLogger.info(root.toGraphString());
return new InsertResult(index, newNode);
}
}
BPlusNode类代码如下:
package com.youngthing.trees.bplus;
import com.youngthing.trees.b.BNode;
import com.youngthing.trees.b.MultiwayNode;
import com.youngthing.trees.b.SortedMultiwayNode;
public class BPlusNode<T extends Comparable<T>> extends BNode<T> {
private BPlusNode<T> prev;
private BPlusNode<T> next;
public BPlusNode(int m) {
super(m);
}
@Override
public void add(T key) {
SortedMultiwayNode<T> leaf = (SortedMultiwayNode<T>) super.findToInsert(key);
if (leaf.isFull()) {
// 那就分裂啊
((BNode<T>) leaf).split();
} else {
leaf.insert(key);
}
}
@Override
public int[] binarySearch(T key) {
// 其实对于二分法做一个修改
int from = 0;
int to = length - 1;
while (to - from > 1) {
int index = (from + to) / 2;
final int c = key.compareTo((T) values[index]);
if (c > 0) {
from = index;
} else if (c < 0) {
to = index;
} else {
return new int[]{index};
}
}
final int f = key.compareTo((T) values[from]);
if (f == 0) {
return new int[]{from};
} else if (f < 0) {
return new int[]{-1, from};
}
final int t = key.compareTo((T) values[to]);
if (t == 0) {
return new int[]{to};
} else if (t < 0) {
return new int[]{from, to};
}
return new int[]{to, to + 1};
}
public void transfer(BPlusNode<T> newNode) {
int t = m / 2;
for (int i = t; i < length; i++) {
newNode.values[i - t] = this.values[i];
}
if (this.isNotLeaf()) {
newNode.initBranches();
}
for (int i = t; i <= length; i++) {
newNode.branches(i - t, branches(i));
}
newNode.length = this.length - t;
// 减少leaf的length
this.length = t;
}
public <T extends Comparable<T>> void setNext(BPlusNode<T> newNode) {
this.next = (BPlusNode) newNode;
if (newNode != null) {
newNode.prev = (BPlusNode) this;
}
}
@Override
protected boolean parentToChild(StringBuilder sb) {
super.parentToChild(sb);
if (this.next != null) {
sb.append(" \"").append(this.getGraphId()).append("\"");
sb.append(" -> ");
sb.append("\"").append(this.next.getGraphId()).append("\"\n");
}
return true;
}
@Override
public void removeKey(int index) {
removeKeyReturn(index);
}
/**
* 带返回值
*
* @param index
*/
public boolean removeKeyReturn(int index) {
if (isNotLeaf() && values[index].equals(branches(index + 1).values[0])) {
return removeInternalKey(index);
} else {
super.removeKey(index);
return false;
}
}
/**
* 寻找所在的节点
*
* @param t
* @return
*/
public MultiwayNode<T> findLeafNode(T t) {
// 二分查找
SortedMultiwayNode<T> x = this;
while (!x.isLeaf()) {
final int[] ints = x.binarySearch(t);
if (ints.length == 1) {
x = (SortedMultiwayNode<T>) x.branches(ints[0] + 1);
} else {
x = (SortedMultiwayNode<T>) x.branches(ints[ints.length - 1]);
}
}
return x;
}
/**
* @param index
* @return true 直接删除了子节点
*/
private boolean removeInternalKey(int index) {
// n >=自己的最小值
BPlusNode<T> n = (BPlusNode<T>) branches(index + 1);
while (n.isNotLeaf()) {
n = (BPlusNode<T>) n.branches(0);
}
final Object replaceValue = n.values[0];
if (!values[index].equals(replaceValue)) {
values[index] = replaceValue;
n.removeLeaf(m / 2, new int[]{0});
} else {
// 这一行有bug 改好了
if (n.length > 1) {
values[index] = n.values[1];
} else {
super.removeKeyWithoutBranches(index);
// 因为右边相等,所以直接去除
final BPlusNode<T> leaf = (BPlusNode<T>) this.branches(index + 1);
leaf.prev.setNext(getNext());
// 直接去除的话,叶子的链表也要维护
removeBranch(index + 1);
return true;
}
// 手动处理,要两个一起删除,还是有bug
// 可以得知,这一定是叶子。
}
return false;
}
public BPlusNode<T> getNext() {
return next;
}
public BPlusNode<T> getPrev() {
return prev;
}
public void borrowRight() {
// 借一个啊
if (next.values[getIndexInParent()].equals(this.getParent().values[getIndexInParent()])) {
// 这里有一个bug
this.getParent().values[getIndexInParent()] = next.values[getIndexInParent() + 1];
}
this.values[this.length] = next.values[0];
next.removeKey(0);
this.length++;
}
public void borrowLeft() {
// 借一个啊
this.insert((T) prev.values[prev.length - 1]);
prev.removeKey(prev.length - 1);
// parent要改成
getParent().values[prev.getIndexInParent()] = this.values[0];
}
private void lendFromRight() {
// 第一,先改parent
final BNode<T> p = getParent();
final MultiwayNode<T> sibling = p.branches(getIndexInParent() + 1);
final Object pvalue = p.values[getIndexInParent()];
// 第一步 先设置好左边
// left
this.values[length++] = pvalue;
this.branches(length, sibling.branches(0));
// parent, 这里是右边最小的
BPlusNode<T> min = (BPlusNode<T>) sibling.branches(getIndexInParent() + 1);
while (min.isNotLeaf()) {
min = (BPlusNode<T>) min.branches(0);
}
p.values[getIndexInParent()] = min.values[0];
// right
sibling.removeKeyWithoutBranches(0);
sibling.removeBranch(0);
// 再进行一个判断
final BPlusNode<T> x = (BPlusNode<T>) sibling.branches(0);
if (x.values[0].equals(pvalue)) {
if (x.isLeaf()) {
x.removeLeaf(m / 2, new int[]{0});
} else {
throw new RuntimeException("还没实现啊");
}
}
}
private void lendFromLeft() {
// 第一,先改parent
final BNode<T> p = getParent();
final MultiwayNode<T> left = p.branches(getIndexInParent() - 1);
final Object pvalue = p.values[getIndexInParent() - 1];
p.values[getIndexInParent() - 1] = left.values[left.length - 1];
// left
left.directRemoveKey(left.length - 1);
// right
this.insert((T) pvalue);
// 所有的向右移动
for (int i = this.length; i >= 0; i--) {
this.branches(i + 1, this.branches(i));
}
this.branches(0, left.branches(left.length + 1));
}
public void removeLeaf(int t, int[] ints) {
if (this.length >= t) {
T key = (T) this.values[ints[ints.length - 1]];
T next = (T) this.values[ints[ints.length - 1] + 1];
BPlusNode<T> x = (BPlusNode<T>) getParent();
while (x != null) {
final int[] indices = x.binarySearch(key);
if (indices.length == 1) {
x.values[indices[0]] = next;
}
x = (BPlusNode<T>) x.getParent();
}
this.removeKeyWithoutBranches(ints[ints.length - 1]);
} else {
// 这种场景需要借一个
final BPlusNode<T> next = this.getNext();
final BPlusNode<T> prev = this.getPrev();
final BPlusNode<T> parent = (BPlusNode<T>) getParent();
if (next != null && next.getParent() == parent && next.length >= t) {
System.out.println("before borrowRight");
System.out.println(getRoot().toGraphString());
this.borrowRight();
System.out.println("after borrowRight");
System.out.println(getRoot().toGraphString());
this.removeKey(ints[ints.length - 1]);
} else if (prev != null && prev.getParent() == parent && prev.length >= t) {
System.out.println("before borrowLeft");
System.out.println(getRoot().toGraphString());
this.borrowLeft();
System.out.println("after borrowLeft");
System.out.println(getRoot().toGraphString());
this.removeKey(ints[ints.length - 1] + 1);
} else {
// merge会减少parent数量
// 所以需要判断
if (!parent.isRoot() && parent.length < t) {
parent.adjust();
System.out.println("after parent adjust:" + parent);
System.out.println(getRoot().toGraphString());
}
if (next != null && next.getParent() == parent) {
System.out.println("before merge");
System.out.println(getRoot().toGraphString());
this.merge();
System.out.println("after merge");
System.out.println(getRoot().toGraphString());
this.removeKey(ints[ints.length - 1]);
} else {
System.out.println("before merge");
System.out.println(getRoot().toGraphString());
this.merge();
System.out.println("after merge");
System.out.println(getRoot().toGraphString());
prev.removeKey(ints[ints.length - 1] + prev.length - this.length);
}
}
}
}
private void adjust() {
int t = m / 2;
final BNode<T> p = getParent();
// 场景分类一定要搞清楚
if (this.getIndexInParent() != p.length && p.branches(getIndexInParent() + 1).length >= t) {
// 从右边借
this.lendFromRight();
} else if (this.getIndexInParent() != 0 && p.branches(getIndexInParent() - 1).length >= t) {
this.lendFromLeft();
} else {
System.out.println("第三种场景");
BPlusNode<T> left;
BPlusNode<T> right;
if (this.getIndexInParent() != p.length) {
left = this;
right = (BPlusNode<T>) p.branches(getIndexInParent() + 1);
} else {
left = (BPlusNode<T>) p.branches(getIndexInParent() - 1);
right = this;
}
BPlusNode<T> parent = (BPlusNode<T>) getParent();
merge(left, right, parent);
}
}
private void merge(BPlusNode<T> left, BPlusNode<T> right, BPlusNode<T> parent) {
T middleValue;
if (parent.values[left.getIndexInParent()].equals(right.values[0])) {
// right 的最小值
BPlusNode<T> x = right;
while (x.isNotLeaf()) {
x = (BPlusNode<T>) right.branches(0);
}
middleValue = (T) x.values[0];
} else {
middleValue = (T) parent.values[left.getIndexInParent()];
}
left.values[left.length++] = middleValue;
left.branches(left.length, right.branches(0));
for (int i = 0; i < right.length; i++) {
left.values[left.length++] = right.values[i];
left.branches(left.length, right.branches(i + 1));
}
// right没了
// 处理parent
parent.removeKeyWithoutBranches(left.getIndexInParent());
parent.removeBranch(left.getIndexInParent() + 1);
}
public void merge() {
BPlusNode<T> left;
BPlusNode<T> right;
if (next != null && next.getParent() == getParent()) {
// 合并右边
left = this;
right = next;
} else if (prev != null && prev.getParent() == getParent()) {
// 合并左边
left = prev;
right = this;
} else {
// 因为B+树至少有2个孩子,所以不可能出现这种场景
throw new RuntimeException("不可能出现这种场景");
}
left.merge(right);
int iip = left.getIndexInParent();
// 删除parent
getParent().removeKeyWithoutBranches(iip);
getParent().removeBranch(iip + 1);
getParent().branches(iip, left);
}
private void merge(BPlusNode<T> right) {
this.setNext(right.next);
final Object p = this.getParent().values[getIndexInParent()];
if (!p.equals(right.values[0])) {
this.insert((T) p);
}
for (int i = 0; i < right.length; i++) {
this.insert((T) right.values[i]);
}
}
@Override
public int insert(T key) {
if (isFull()) {
throw new RuntimeException();
}
final int index = nextIndex(key);
// 要全部移动啊
// 比如 2 5 , insert 3 -> 2 3 5
if (!isLeaf() && index < length) {
branches(length + 1, branches(length));
}
for (int i = length; i > index; i--) {
values[i] = values[i - 1];
}
if (!isLeaf() && index < length) {
for (int i = Math.min(length + 2, m - 1); i - 1 > index; i--) {
branches(i, branches(i - 1));
}
}
values[index] = key;
super.length++;
return index;
}
@Override
protected void ports(StringBuilder sb) {
if (isLeaf()) {
return;
}
super.ports(sb);
}
}