说起B+树索引,就不得不提二叉查找树,平衡二叉树和B树这三种数据结构。B+树就是从他们仨演化来的。
在MySQL中,B+树索引按照存储方式的不同分为聚集索引和非聚集索引。
1. 聚集索引(聚簇索引):以innodb作为存储引擎的表,表中的数据都会有一个主键,即使你不创建主键,系统也会帮你创建一个隐式的主键。这是因为innodb是把数据存放在B+树中的,而B+树的键值就是主键,在B+树的叶子节点中,存储了表中所有的数据。这种以主键作为B+树索引的键值而构建的B+树索引,我们称之为聚集索引。
2. 非聚集索引(非聚簇索引):以主键以外的列值作为键值构建的B+树索引,我们称之为非聚集索引。非聚集索引与聚集索引的区别在于非聚集索引的叶子节点不存储表中的数据,而是存储该列对应的主键,想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们称为回表。
明白了聚集索引和非聚集索引的定义,我们应该明白这样一句话:数据即索引,索引即数据。
利用聚集索引和非聚集索引查找数据
前面我们讲解B+树索引的时候并没有去说怎么在B+树中进行数据的查找,主要就是因为还没有引出聚集索引和非聚集索引的概念。下面我们通过讲解如何通过聚集索引以及非聚集索引查找数据表中数据的方式介绍一下B+树索引查找数据方法。
利用聚集索引查找数据
现在假设我们要查找id>=18并且id<40的用户数据。对应的sql语句为select * from user where id>=18 and id <40,其中id为主键。具体的查找过程如下:
-
1. 一般根节点都是常驻内存的,也就是说页1已经在内存中了,此时不需要到磁盘中读取数据,直接从内存中读取即可。
从内存中读取到页1,要查找这个id>=18 and id <40或者范围值,我们首先需要找到id=18的键值。
从页1中我们可以找到键值18,此时我们需要根据指针p2,定位到页3。
-
2. 要从页3中查找数据,我们就需要拿着p2指针去磁盘中进行读取页3。
从磁盘中读取页3后将页3放入内存中,然后进行查找,我们可以找到键值18,然后再拿到页3中的指针p1,定位到页8。
-
3. 同样的页8页不在内存中,我们需要再去磁盘中将页8读取到内存中。
将页8读取到内存中后。
因为页中的数据是链表进行连接的,而且键值是按照顺序存放的,此时可以根据二分查找法定位到键值18。
此时因为已经到数据页了,此时我们已经找到一条满足条件的数据了,就是键值18对应的数据。
因为是范围查找,而且此时所有的数据又都存在叶子节点,并且是有序排列的,那么我们就可以对页8中的键值依次进行遍历查找并匹配满足条件的数据。
我们可以一直找到键值为22的数据,然后页8中就没有数据了,此时我们需要拿着页8中的p指针去读取页9中的数据。
-
4. 因为页9不在内存中,就又会加载页9到内存中,并通过和页8中一样的方式进行数据的查找,直到将页12加载到内存中,发现41大于40,此时不满足条件。
那么查找到此终止。
最终我们找到满足条件的所有数据为:
(18,kl),(19,kl),(22,hj),(24,io),(25,vg),(29,jk),(31,jk),(33,rt),(34,ty),(35,yu),(37,rt),(39,rt)。
总共12条记录。
下面看下具体的查找流程图:
利用非聚集索引查找数据
查找的流程跟聚集索引一样,这里就不详细介绍了。我们最终会找到主键值47,找到主键后我们需要再到聚集索引中查找具体对应的数据信息,此时又回到了聚集索引的查找流程。
下面看下具体的查找流程图:
在MyISAM中,聚集索引和非聚集索引的叶子节点都会存储数据的文件地址。
java代码实现B+树的效果
实现思路
1.节点的结构(如下图)
(1)键值对--key是标识;value是存储的具体数据
(2)节点的子节点--存储的是具体的子节点
(3)节点的后节点--标记后一个节点
(4)节点的前节点--标记前一个节点
(5)节点的父节点--标记父节点是哪个
2.存储的规则
2.1存储第一条数据,并标记根节点和头结点。
2.2单个节点
2.2.1.没有超过设定的阶数
当存储第二和三个数据时,首先判断是否是最后一个结点或者要插入的键值对的键的值是否小于下一个结点的的键的最小值。如果是则再判断有没有超过设定的阶数,没有则将将数据直接插入到当前结点。当前的结点是最后一个结点并且没有超过设定的阶数,因此直接将二三个数据直接插入到当前的结点当中 。
2.2.2.超过阶数(分裂)
在存储第四个数时,首先判断是否是最后一个结点或者要插入的键值对的键的值是否小于下一个结点的的键的最小值。如果是再判断是否超过设定的阶数了,超过了则取出原来key-value 集合中间位置的下标mid并获得中间位置的键midKey。构造一个新的键值对midKeyAndValue存储(中间位置的键,空串),然后分别将中间位置的左边封装成集合对象leftKeyAndValue,并将左边的数存储到leftKeyAndValue中;中间位置的右边封装成集合对象rightKeyAndValue,再判断当前节点是否有叶子结点,如果有则将中间位置后的数据(不包含中间位置的数据)存储到rightKeyAndValue中;如果没有则将从中间位置开始右边的数据保存到rightKeyAndValue中。分别对左右两个集合对象进行排序处理。以mid为界限将当前结点分裂成两个结点分别是:前节点leftNode,后节点rightNode;前指针的节点的结构为:数据(leftKeyAndValue),子节点(null),前指针(当前节点的左节点),后指针(rightNode),父节点(当前结点的父节点);此时将头节点重置为前节点leftNode;新建一个子节点childNode并将前节点leftNode和后节点rightNode添加进去,然后构造一个父节点parentNode结构为:子节点(childNode),键值对(midKeyAndValue),前节点(null),后节点(null),父节点(null)。并将子节点与父节点进行关联。将当前父节点设置为根节点。此时就转变成“一父二子”了
2.3一夫二子
2.3.1.没有超过设定的阶数
存储第五个数的时候,首先以L1节点为head判断是否是最后一个结点或者要插入的键值对的键的值是否小于下一个结点的的键的最小值。如果是再判断是否超过设定的阶数了,根据上图可以看出无论数据存储到哪个子节点都不会超过设定的阶数,因此可以将数据直接存储到L1节点;如果不是最后一个结点或者要插入的键值对的键的值大于下一个结点的的键的最小值,此时移动指针,将R1作为依据再进行判断是否是最后一个结点或者要插入的键值对的键的值是否小于下一个结点的的键的最小值,此时肯定符合条件的,再判断是否超过设定的阶数了,此时的场景不会超过设定的阶数,因此将数据直接添加到R1中,并排序处理。
因此会有下图这两种情况
存储第六个数据的时候有百分之五十的几率不会超过设定的阶数存储的过程跟存储第五个数的过程一样。
结果如下图两种情况
2.3.2超过设定的阶数
存储第六个数据的时候有百分之五十的可能性会超过阶数,如下图(2.3-5和2.3-6),在添加数据的时候首先以L1节点为head判断是否是最后一个结点或者要插入的键值对的键的值是否小于下一个结点的的键的最小值。
1.如果是再判断是否超过设定的阶数了,此时设定的场景是要超过设定的阶数,先取出L1节点的key-value 集合中间位置的下标mid并获得中间位置的键midKey。构造一个新的键值对midKeyAndValue存储(中间位置的键,空串),然后分别将中间位置的左边封装成集合对象leftKeyAndValue,并将左边的数存储到leftKeyAndValue中;中间位置的右边封装成集合对象rightKeyAndValue,再判断当前节点是否有叶子结点,如果有则将中间位置后的数据(不包含中间位置的数据)存储到rightKeyAndValue中;如果没有则将从中间位置开始右边的数据保存到rightKeyAndValue中。分别对左右两个集合对象进行排序处理。以mid为界限将当前结点分裂成两个结点分别是:前节点leftNode,后节点rightNode;前指针的节点的结构为:数据(leftKeyAndValue),子节点(null),前指针(当前节点的左节点),后指针(rightNode),父节点(当前结点的父节点),新建一个子节点集合childNodes并分别将前节点leftNode,后节点rightNode添加进去;如果头结点是当前要分隔的节点则将头节点重置为前节点leftNode,获取到当前L1节点的父节点parentNode,并获取到父节点的所有子节点,将这些子节点全部添加到子节点集合childNodes中,然后删除当前的L1节点,然后将子节点重置成新的子节点集合childNodes;继续以父节点为依据判断是否超过设定的阶数了,此时没有超过阶数,将键值对midKeyAndValue直接保存到当前的父节点中,并进行排序操作,此时就变成“一夫三子”了如图(2.3-7);
2.如果不是最后一个结点或者要插入的键值对的键的值大于下一个结点的的键的最小值,此时移动指针,将R1作为依据再进行判断是否是最后一个结点或者要插入的键值对的键的值是否小于下一个结点的的键的最小值,此时肯定符合条件的,再判断是否超过设定的阶数了,此时设定的场景是要超过设定的阶数,先取出R1节点的key-value 集合中间位置的下标mid并获得中间位置的键midKey。构造一个新的键值对midKeyAndValue存储(中间位置的键,空串),然后分别将中间位置的左边封装成集合对象leftKeyAndValue,并将左边的数存储到leftKeyAndValue中;中间位置的右边封装成集合对象rightKeyAndValue,再判断当前节点是否有叶子结点,如果有则将中间位置后的数据(不包含中间位置的数据)存储到rightKeyAndValue中;如果没有则将从中间位置开始右边的数据保存到rightKeyAndValue中。分别对左右两个集合对象进行排序处理。以mid为界限将当前结点分裂成两个结点分别是:前节点leftNode,后节点rightNode;前指针的节点的结构为:数据(leftKeyAndValue),子节点(null),前指针(当前节点的左节点),后指针(rightNode),父节点(当前结点的父节点),新建一个子节点集合childNodes并分别将前节点leftNode,后节点rightNode添加进去;如果头结点是当前要分隔的节点则将头节点重置为前节点leftNode,获取到当前L1节点的父节点parentNode,并获取到父节点的所有子节点,将这些子节点全部添加到子节点集合childNodes中,然后删除当前的R1节点,然后将子节点重置成新的子节点集合childNodes;继续以父节点为依据判断是否超过设定的阶数了,此时没有超过阶数,将键值对midKeyAndValue直接保存到当前的父节点中,并进行排序操作,此时就变成“一夫三子”了如图(2.3-8)
存储第七个数据时有两种情况
1.如果存储第六个数据时没有分裂,则此时存储定会分裂如下图(2.3.9-2.3.12);分裂过程与第六次存储数据需要分裂的过程一样,结果如下图
2.如果存储第六个数据时分裂了,则此时定不会分裂结果入下图(2.3.13-2.3.16),
首先分别以图2.3-7的L2节点和2.3-8的L1节点为head判断是否是最后一个结点或者要插入的键值对的键的值是否小于下一个结点的的键的最小值。
(1).如果是再判断是否超过设定的阶数了,根据上图可以看出无论数据存储到哪个子节点都不会超过设定的阶数,因此可以将数据直接存储到第一个节点;
(2).如果不是最后一个结点或者要插入的键值对的键的值大于下一个结点的的键的最小值,此时移动指针,将第二个作为依据再进行判断是否是最后一个结点或者要插入的键值对的键的值是否小于下一个结点的的键的最小值,如果是则判断是否超过设定的阶数,此场景不会超过阶数,直接将数据存储到第二个节点的键值对当中;
(3).如果还不是最后一个结点或者要插入的键值对的键的值大于下一个结点的的键的最小值,此时移动指针,将第三个作为依据再进行判断是否是最后一个结点或者要插入的键值对的键的值是否小于下一个结点的的键的最小值, 再判断是否超过设定的阶数了,此时的场景不会超过设定的阶数,因此将数据直接添加到第三个节点中,并排序处理。
2.4一父三子
2.4.1 三个子节点的数,要么都满,要么部分满
存储更多数据的时候,三子节点的键值对的数据有以下两种情况
1.都满,最少再添加四个数据才会进行分裂(如图2.4.1)分裂成一父四子(如图 2.4-3)
2.部分满,再上面的基础上最少添加两个数据,就会出现部分满的情况(如图2.4-2),此时的情况是需要将满的节点进行分裂,过程与前面分裂过程一样,变成一父四子(如图2.4-4)
2.5一父四子
2.5.1 四个子节点的数,要么都满,要么部分满
部分满跟都满都会使得节点再次分裂,分裂成一父二子的情况
在添加数据的时候首先以左边第一个节点为head判断是否是最后一个结点或者要插入的键值对的键的值是否小于下一个结点的的键的最小值。
1.如果是再判断是否超过设定的阶数了,此时设定的场景是要超过设定的阶数,先取出当前节点的key-value 集合中间位置的下标mid并获得中间位置的键midKey。构造一个新的键值对midKeyAndValue存储(中间位置的键,空串),然后分别将中间位置的左边封装成集合对象leftKeyAndValue,并将左边的数存储到leftKeyAndValue中;中间位置的右边封装成集合对象rightKeyAndValue,再判断当前节点是否有叶子结点,如果有则将中间位置后的数据(不包含中间位置的数据)存储到rightKeyAndValue中;如果没有则将从中间位置开始右边的数据保存到rightKeyAndValue中。分别对左右两个集合对象进行排序处理。以mid为界限将当前结点分裂成两个结点分别是:前节点leftNode,后节点rightNode;前指针的节点的结构为:数据(leftKeyAndValue),子节点(null),前指针(当前节点的左节点),后指针(rightNode),父节点(当前结点的父节点),新建一个子节点集合childNodes并分别将前节点leftNode,后节点rightNode添加进去;如果头结点是当前要分隔的节点则将头节点重置为前节点leftNode,获取到当前节点的父节点parentNode,并获取到父节点的所有子节点,将这些子节点全部添加到子节点集合childNodes中,然后删除当前节点,然后将子节点重置成新的子节点集合childNodes;
2.继续以父节点为依据判断是否超过设定的阶数了,此时超过阶数,超过了则取出原来key-value 集合中间位置的下标mid并获得中间位置的键midKey。构造一个新的键值对midKeyAndValue存储(中间位置的键,空串),然后分别将中间位置的左边封装成集合对象leftKeyAndValue,并将左边的数存储到leftKeyAndValue中;中间位置的右边封装成集合对象rightKeyAndValue,再判断当前节点是否有叶子结点,如果有则将中间位置后的数据(不包含中间位置的数据)存储到rightKeyAndValue中;如果没有则将从中间位置开始右边的数据保存到rightKeyAndValue中。分别对左右两个集合对象进行排序处理。以mid为界限将当前结点分裂成两个结点分别是:前节点leftNode,后节点rightNode;前指针的节点的结构为:数据(leftKeyAndValue),子节点(null),前指针(当前节点的左节点),后指针(rightNode),父节点(当前结点的父节点);判断当前结点是否有孩子节点,此场景是有孩子节点的,获取到所有孩子节点存储在nodes集合中,并新建两个集合leftNodes与rightNodes分别存储左节点的子节点与右节点的子节点,通过遍历取得当前孩子节点的最大键值,小于mid的键的数是左节点的子节点;大于mid的键的数是右节点的子节点,将leftNodes添加为leftNode的子节点;将rightNodes添加为rightNode的子节点。此时将头节点重置为前节点leftNode;新建一个子节点childNode并将前节点leftNode和后节点rightNode添加进去;然后判断当前结点是否有父节点,此时有父节点,获取到当前节点的父节点parentNode,并获取到父节点的所有子节点,将这些子节点全部添加到子节点集合childNodes中,然后删除当前的节点,然后将子节点重置成新的子节点集合childNodes;继续以父节点为依据判断是否超过设定的阶数了,此时没有超过阶数,将键值对midKeyAndValue直接保存到当前的父节点中,并进行排序操作。最终效果如下图所示()
具体代码实现
1、创建一个B+树的容器对象Node.java
package com.bsh.test.bTree;
import java.util.List;
/*节点类*/
public class Node {
//children
//节点的子节点
private List<Node> nodes;
//节点的键值对
private List<KeyAndValue> keyAndValue;
//节点的后节点
private Node nextNode;
//节点的前节点
private Node previousNode;
//节点的父节点
private Node parantNode;
public Node( List<Node> nodes, List<KeyAndValue> keyAndValue, Node nextNode,Node previousNode, Node parantNode) {
this.nodes = nodes;
this.keyAndValue = keyAndValue;
this.nextNode = nextNode;
this.parantNode = parantNode;
this.previousNode = previousNode;
}
boolean isLeaf() {
return nodes==null;
}
boolean isHead() {
return previousNode == null;
}
boolean isTail() {
return nextNode == null;
}
boolean isRoot() {
return parantNode == null;
}
List<Node> getNodes() {
return nodes;
}
void setNodes(List<Node> nodes) {
this.nodes = nodes;
}
List<KeyAndValue> getKeyAndValue() {
return keyAndValue;
}
Node getNextNode() {
return nextNode;
}
void setNextNode(Node nextNode) {
this.nextNode = nextNode;
}
Node getParantNode() {
return parantNode;
}
void setParantNode(Node parantNode) {
this.parantNode = parantNode;
}
Node getPreviousNode() {
return previousNode;
}
void setPreviousNode(Node previousNode) {
this.previousNode = previousNode;
}
}
2.创建一个数据存储对象
package com.bsh.test.bTree;
import java.util.ArrayList;
import java.util.Collections;
public class KeyAndValue implements Comparable<KeyAndValue>{
/*存储索引关键字*/
private int key;
/*存储数据*/
private Object value;
@Override
public int compareTo(KeyAndValue o) {
//根据key的值升序排列 从小到大
// 1 3 4 6
// 0 1 2 3
return this.key - o.key;
}
public int getKey() {
return key;
}
public void setKey(int key) {
this.key = key;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
KeyAndValue(int key, Object value) {
this.key = key;
this.value = value;
}
}
3.逻辑代码
package com.bsh.test.bTree;
import java.util.*;
public class Btree {
private static final String NODE = "NODE";
static final String INT = "INT";
private static final String PRENODE = "PRENODE";
private static final String NEXTNODE = "NEXTNODE";
//B+树的阶数
private int rank;
//根节点
private Node root;
//头结点
private Node head;
Btree(int rank) {
this.rank = rank;
}
public Node getRoot() {
return root;
}
public void insert(KeyAndValue entry) {
List<KeyAndValue> keyAndValues1 = new ArrayList<>();
//插入第一个节点
if (head == null) {
keyAndValues1.add(entry);
head = new Node(null, keyAndValues1, null, null, null);
root = new Node(null, keyAndValues1, null, null, null);
} else {
Node node = head;
//遍历链表,找到插入键值对对应的节点
while (node != null) {
List<KeyAndValue> keyAndValues = node.getKeyAndValue();
// 如果插入的键的值和当前节点键值对集合中的某个键的值相等,则直接替换value(相当于更新数据)
for (KeyAndValue KV : keyAndValues) {
if (KV.getKey() == entry.getKey()) {
KV.setValue(entry.getValue());
return;
}
}
//如果当前节点是最后一个节点或者要插入的键值对的键的值小于下一个节点的键的最小值,则直接插入当前节点
if (node.getNextNode() == null || node.getNextNode().getKeyAndValue().get(0).getKey() >= entry.getKey()) {
splidNode(node, entry);
break;
}
//移动指针
node = node.getNextNode();
}
}
}
//判断是否需要拆分节点
private void splidNode(Node node, KeyAndValue addkeyAndValue) {
List<KeyAndValue> keyAndValues = node.getKeyAndValue();
if (keyAndValues.size() == rank - 1) {
//先插入待添加的节点
keyAndValues.add(addkeyAndValue);
//排序
Collections.sort(keyAndValues);
//取出当前节点的键值对集合
//取出原来的key-value集合中间位置的下标
int mid = keyAndValues.size() / 2;
//取出原来的key-value集合中间位置的键
int midKey = keyAndValues.get(mid).getKey();
//构造一个新的键值对,不是叶子节点的节点不存储value的信息
KeyAndValue midKeyAndValue = new KeyAndValue(midKey, "");
//将中间位置左边的键值对封装成集合对象
List<KeyAndValue> leftKeyAndValues = new ArrayList<>();
for (int i = 0; i < mid; i++) {
leftKeyAndValues.add(keyAndValues.get(i));
}
//将中间位置右边边的键值对封装成集合对象
List<KeyAndValue> rightKeyAndValues = new ArrayList<>();
//如果是叶子节点则在原节点中保留上移的key-value,否则原节点删除上移的key-value
int k;
if (node.isLeaf()) {
k = mid;
} else {
k = mid + 1;
}
for (int i = k; i < rank; i++) {
rightKeyAndValues.add(keyAndValues.get(i));
}
//对左右两边的元素重排序
Collections.sort(leftKeyAndValues);
Collections.sort(rightKeyAndValues);
//以mid为界限将当前节点分列成两个节点,维护前指针和后指针
Node rightNode;
Node leftNode;
//如果是叶子节点维护前后指针
rightNode = new Node(null, rightKeyAndValues, node.getNextNode(), null, node.getParantNode());
leftNode = new Node(null, leftKeyAndValues, rightNode, node.getPreviousNode(), node.getParantNode());
rightNode.setPreviousNode(leftNode);
//如果当前分裂的节点有孩子节点,设置分裂后节点和孩子节点的关系
if (node.getNodes() != null) {
//取得所有地孩子节点
List<Node> nodes = node.getNodes();
List<Node> leftNodes = new ArrayList<>();
List<Node> rightNodes = new ArrayList<>();
for (Node childNode : nodes) {
//取得当前孩子节点的最大键值
int max = childNode.getKeyAndValue().get(childNode.getKeyAndValue().size() - 1).getKey();
if (max < midKeyAndValue.getKey()) {
//小于mid处的键的数是左节点的子节点
leftNodes.add(childNode);
childNode.setParantNode(leftNode);
} else {
//大于mid处的键的数是右节点的子节点
rightNodes.add(childNode);
childNode.setParantNode(rightNode);
}
}
leftNode.setNodes(leftNodes);
rightNode.setNodes(rightNodes);
}
//当前节点的前节点
Node preNode = node.getPreviousNode();
//分裂节点后将分裂节点的前节点的后节点设置为左节点
if (preNode != null) {
preNode.setNextNode(leftNode);
}
//当前节点的后节点
Node nextNode = node.getNextNode();
//分裂节点后将分裂节点的后节点的前节点设置为右节点
if (nextNode != null) {
nextNode.setPreviousNode(rightNode);
}
//如果由头结点分裂,则分裂后左边的节点为头节点
if (node == head) {
head = leftNode;
}
//父节点的子节点
List<Node> childNodes = new ArrayList<>();
childNodes.add(rightNode);
childNodes.add(leftNode);
//分裂
//当前节点无父节点
if (node.getParantNode() == null) {
//父节点的键值对
List<KeyAndValue> parentKeyAndValues = new ArrayList<>();
parentKeyAndValues.add(midKeyAndValue);
//构造父节点
Node parentNode = new Node(childNodes, parentKeyAndValues, null, null, null);
//将子节点与父节点关联
rightNode.setParantNode(parentNode);
leftNode.setParantNode(parentNode);
//当前节点为根节点
root = parentNode;
} else {
Node parentNode = node.getParantNode();
//将原来的孩子节点(除了被拆分的节点)和新的孩子节点(左孩子和右孩子)合并之后与父节点关联
childNodes.addAll(parentNode.getNodes());
//移除正在被拆分的节点
childNodes.remove(node); // 为什么要移除node?
//将子节点与父节点关联
parentNode.setNodes(childNodes);
rightNode.setParantNode(parentNode);
leftNode.setParantNode(parentNode);
if (parentNode.getParantNode() == null) {
root = parentNode;
}
//当前节点有父节点,递归调用拆分的方法,将父节点拆分
splidNode(parentNode, midKeyAndValue);
}
} else {
keyAndValues.add(addkeyAndValue);
//排序
Collections.sort(keyAndValues);
}
}
//打印B+树
void printBtree(Node root) {
if (root == this.root) {
//打印根节点内的元素
printNode(root);
System.out.println();
}
if (root == null) {
return;
}
//打印子节点的元素
if (root.getNodes() != null) {
//找到最左边的节点
Node leftNode = null;
Node tmpNode = null;
List<Node> childNodes = root.getNodes();
for (Node node : childNodes) {
if (node.getPreviousNode() == null) {
leftNode = node;
tmpNode = node;
}
}
while (leftNode != null) {
//从最左边的节点向右打印
printNode(leftNode);
System.out.print("|");
leftNode = leftNode.getNextNode();
}
System.out.println();
printBtree(tmpNode);
}
}
//打印一个节点内的元素
private void printNode(Node node) {
List<KeyAndValue> keyAndValues = node.getKeyAndValue();
for (int i = 0; i < keyAndValues.size(); i++) {
if (i != (keyAndValues.size() - 1)) {
System.out.print(keyAndValues.get(i).getKey() + ",");
} else {
System.out.print(keyAndValues.get(i).getKey());
}
}
}
public Object search(int key, Node node, String mode) {
//如果是叶子节点则直接取值
if (node.isLeaf()) {
List<KeyAndValue> keyAndValues = node.getKeyAndValue();
for (KeyAndValue keyAndValue : keyAndValues) {
if (keyAndValue.getKey() == key) {
switch (mode) {
case NODE:
return node;
case INT:
return keyAndValue.getValue();
}
}
}
return null;
}
List<Node> nodes = node.getNodes();
//如果寻找的key小于节点的键的最小值
int minKey = node.getKeyAndValue().get(0).getKey();
if (key < minKey) {
for (Node n : nodes) {
List<KeyAndValue> keyAndValues = n.getKeyAndValue();
//找到子节点集合中最大键小于父节点最小键节点
if (keyAndValues.get(keyAndValues.size() - 1).getKey() < minKey) {
return search(key, n, mode);
}
}
}
//如果寻找的key大于节点的键的最大值
int maxKey = getMaxKeyInNode(node);
if (key >= maxKey) {
for (Node n : nodes) {
List<KeyAndValue> keyAndValues = n.getKeyAndValue();
//找到子节点集合中最小键大于等于父节点最小大键节点
if (keyAndValues.get(0).getKey() >= maxKey) {
return search(key, n, mode);
}
}
}
//如果寻找的key在最大值和最小值之间,首先定位到最窄的区间
int min = getLeftBoundOfKey(node, key);
int max = getRightBoundOfKey(node, key);
//去所有的子节点中找键的范围在min和max之间的节点
for (Node n : nodes) {
List<KeyAndValue> kvs = n.getKeyAndValue();
//找到子节点集合中键的范围在min和max之间的节点
if (kvs.get(0).getKey() >= min && kvs.get(kvs.size() - 1).getKey() < max) {
return search(key, n, mode);
}
}
return null;
}
public boolean delete(int key) {
System.out.println("delete:" + key);
System.out.println();
//首先找到要删除的key所在的节点
Node deleteNode = (Node) search(key, root, NODE);
//如果没找到则删除失败
if (deleteNode == null) {
return false;
}
if (deleteNode == root) {
delKeyAndValue(root.getKeyAndValue(), key);
return true;
}
if (deleteNode == head && isNeedMerge(head)) {
head = head.getNextNode();
}
return merge(deleteNode, key);
}
//平衡当前节点和前节点或者后节点的数量,使两者的数量都满足条件
private boolean balanceNode(Node node, Node bratherNode, String nodeType) {
if (bratherNode == null) {
return false;
}
List<KeyAndValue> delKeyAndValues = node.getKeyAndValue();
if (isMoreElement(bratherNode)) {
List<KeyAndValue> bratherKeyAndValues = bratherNode.getKeyAndValue();
int bratherSize = bratherKeyAndValues.size();
//兄弟节点删除挪走的键值对
KeyAndValue keyAndValue = null;
KeyAndValue keyAndValue1;
switch (nodeType) {
case PRENODE:
keyAndValue = bratherKeyAndValues.remove(bratherSize - 1);
keyAndValue1 = getKeyAndValueinMinAndMax(node.getParantNode(), keyAndValue.getKey(), getMinKeyInNode(node));
keyAndValue1.setKey(keyAndValue.getKey());
break;
case NEXTNODE:
keyAndValue = bratherKeyAndValues.remove(0);
keyAndValue1 = getKeyAndValueinMinAndMax(node.getParantNode(), getMaxKeyInNode(node), keyAndValue.getKey());
keyAndValue1.setKey(bratherKeyAndValues.get(0).getKey());
break;
}
//当前节点添加从前一个节点得来的键值对
delKeyAndValues.add(keyAndValue);
//对键值对重排序
Collections.sort(delKeyAndValues);
return true;
}
return false;
}
public boolean merge(Node node, int key) {
List<KeyAndValue> delKeyAndValues = node.getKeyAndValue();
//首先删除该key-vaule
delKeyAndValue(delKeyAndValues, key);
//如果要删除的节点的键值对的数目小于节点最大键值对数目*填充因子
if (isNeedMerge(node)) {
Boolean isBalance;
//如果左节点有富余的键值对,则取一个到当前节点
Node preNode = getPreviousNode(node);
isBalance = balanceNode(node, preNode, PRENODE);
//如果此时已经平衡,则已经删除成功
if (isBalance) return true;
//如果右兄弟节点有富余的键值对,则取一个到当前节点
Node nextNode = getNextNode(node);
isBalance = balanceNode(node, nextNode, NEXTNODE);
return isBalance || mergeNode(node, key);
} else {
return true;
}
}
//合并节点
//key 待删除的key
private boolean mergeNode(Node node, int key) {
if (node.isRoot()) {
return false;
}
Node preNode;
Node nextNode;
Node parentNode = node.getParantNode();
List<Node> childNodes = parentNode.getNodes();
List<Node> childNodes1 = node.getNodes();
List<KeyAndValue> parentKeyAndValue = parentNode.getKeyAndValue();
List<KeyAndValue> keyAndValues = node.getKeyAndValue();
if (node.isLeaf()) {
if (parentKeyAndValue.size() == 1 && parentNode != root) {
return true;
}
preNode = getPreviousNode(node);
nextNode = getNextNode(node);
if (preNode != null) {
List<KeyAndValue> preKeyAndValues = preNode.getKeyAndValue();
keyAndValues.addAll(preKeyAndValues);
if (preNode.isHead()) {
head = node;
node.setPreviousNode(null);
} else {
preNode.getPreviousNode().setNextNode(node);
node.setPreviousNode(preNode.getPreviousNode());
}
//将合并后节点的后节点设置为当前节点的后节点
preNode.setNextNode(node.getNextNode());
KeyAndValue keyAndValue = getKeyAndValueinMinAndMax(parentNode, getMinKeyInNode(preNode), key);
delKeyAndValue(parentKeyAndValue, keyAndValue.getKey());
if (parentKeyAndValue.isEmpty()) {
root = node;
} else {
//删除当前节点
childNodes.remove(preNode);
}
Collections.sort(keyAndValues);
merge(parentNode, key);
return true;
}
if (nextNode != null) {
List<KeyAndValue> nextKeyAndValues = nextNode.getKeyAndValue();
keyAndValues.addAll(nextKeyAndValues);
if (nextNode.isTail()) {
node.setPreviousNode(null);
} else {
nextNode.getNextNode().setPreviousNode(node);
node.setNextNode(nextNode.getNextNode());
}
KeyAndValue keyAndValue = getKeyAndValueinMinAndMax(parentNode, key, getMinKeyInNode(nextNode));
delKeyAndValue(parentKeyAndValue, keyAndValue.getKey());
if (parentKeyAndValue.isEmpty()) {
root = node;
node.setParantNode(null);
} else {
//删除当前节点
childNodes.remove(nextNode);
}
Collections.sort(keyAndValues);
merge(parentNode, key);
return true;
}
//前节点和后节点都等于null那么是root节点
return false;
} else {
preNode = getPreviousNode(node);
nextNode = getNextNode(node);
if (preNode != null) {
//将前一个节点和当前节点还有父节点中的相应Key-value合并
List<KeyAndValue> preKeyAndValues = preNode.getKeyAndValue();
preKeyAndValues.addAll(keyAndValues);
int min = getMaxKeyInNode(preNode);
int max = getMinKeyInNode(node);
//父节点中移除这个key-value
KeyAndValue keyAndValue = getKeyAndValueinMinAndMax(parentNode, min, max);
parentKeyAndValue.remove(keyAndValue);
if (parentKeyAndValue.isEmpty()) {
root = preNode;
node.setParantNode(null);
preNode.setParantNode(null);
} else {
childNodes.remove(node);
}
assert nextNode != null;
preNode.setNextNode(nextNode.getNextNode());
//前节点加上一个当前节点的所有子节点中最小key的key-value
KeyAndValue minKeyAndValue = getMinKeyAndValueInChildNode(node);
assert minKeyAndValue != null;
KeyAndValue keyAndValue1 = new KeyAndValue(minKeyAndValue.getKey(), minKeyAndValue.getValue());
preKeyAndValues.add(keyAndValue1);
List<Node> preChildNodes = preNode.getNodes();
preChildNodes.addAll(node.getNodes());
//将当前节点的孩子节点的父节点设为当前节点的后节点
for (Node node1 : childNodes1) {
node1.setParantNode(preNode);
}
Collections.sort(preKeyAndValues);
merge(parentNode, key);
return true;
}
if (nextNode != null) {
//将后一个节点和当前节点还有父节点中的相应Key-value合并
List<KeyAndValue> nextKeyAndValues = nextNode.getKeyAndValue();
nextKeyAndValues.addAll(keyAndValues);
int min = getMaxKeyInNode(node);
int max = getMinKeyInNode(nextNode);
//父节点中移除这个key-value
KeyAndValue keyAndValue = getKeyAndValueinMinAndMax(parentNode, min, max);
parentKeyAndValue.remove(keyAndValue);
childNodes.remove(node);
if (parentKeyAndValue.isEmpty()) {
root = nextNode;
nextNode.setParantNode(null);
} else {
childNodes.remove(node);
}
nextNode.setPreviousNode(node.getPreviousNode());
//后节点加上一个当后节点的所有子节点中最小key的key-value
KeyAndValue minKeyAndValue = getMinKeyAndValueInChildNode(nextNode);
assert minKeyAndValue != null;
KeyAndValue keyAndValue1 = new KeyAndValue(minKeyAndValue.getKey(), minKeyAndValue.getValue());
nextKeyAndValues.add(keyAndValue1);
List<Node> nextChildNodes = nextNode.getNodes();
nextChildNodes.addAll(node.getNodes());
//将当前节点的孩子节点的父节点设为当前节点的后节点
for (Node node1 : childNodes1) {
node1.setParantNode(nextNode);
}
Collections.sort(nextKeyAndValues);
merge(parentNode, key);
return true;
}
return false;
}
}
//得到当前节点的前节点
private Node getPreviousNode(Node node) {
if (node.isRoot()) {
return null;
}
Node parentNode = node.getParantNode();
//得到兄弟节点
List<Node> nodes = parentNode.getNodes();
List<KeyAndValue> keyAndValues = new ArrayList<>();
for (Node n : nodes) {
List<KeyAndValue> list = n.getKeyAndValue();
int maxKeyAndValue = list.get(list.size() - 1).getKey();
if (maxKeyAndValue < getMinKeyInNode(node)) {
keyAndValues.add(new KeyAndValue(maxKeyAndValue, n));
}
}
Collections.sort(keyAndValues);
if (keyAndValues.isEmpty()) {
return null;
}
return (Node) keyAndValues.get(keyAndValues.size() - 1).getValue();
}
//得到当前节点的后节点
private Node getNextNode(Node node) {
if (node.isRoot()) {
return null;
}
Node parentNode = node.getParantNode();
//得到兄弟节点
List<Node> nodes = parentNode.getNodes();
List<KeyAndValue> keyAndValues = new ArrayList<>();
for (Node n : nodes) {
List<KeyAndValue> list = n.getKeyAndValue();
int minKeyAndValue = list.get(0).getKey();
if (minKeyAndValue > getMaxKeyInNode(node)) {
keyAndValues.add(new KeyAndValue(minKeyAndValue, n));
}
}
Collections.sort(keyAndValues);
if (keyAndValues.isEmpty()) {
return null;
}
return (Node) keyAndValues.get(0).getValue();
}
private int getMinKeyInNode(Node node) {
List<KeyAndValue> keyAndValues = node.getKeyAndValue();
return keyAndValues.get(0).getKey();
}
private int getMaxKeyInNode(Node node) {
List<KeyAndValue> keyAndValues = node.getKeyAndValue();
return keyAndValues.get(keyAndValues.size() - 1).getKey();
}
private int getLeftBoundOfKey(Node node, int key) {
int left = 0;
List<KeyAndValue> keyAndValues = node.getKeyAndValue();
for (int i = 0; i < keyAndValues.size(); i++) {
if (keyAndValues.get(i).getKey() <= key && keyAndValues.get(i + 1).getKey() > key) {
left = keyAndValues.get(i).getKey();
break;
}
}
return left;
}
private int getRightBoundOfKey(Node node, int key) {
int right = 0;
List<KeyAndValue> keyAndValues = node.getKeyAndValue();
for (int i = 0; i < keyAndValues.size(); i++) {
if (keyAndValues.get(i).getKey() <= key && keyAndValues.get(i + 1).getKey() > key) {
right = keyAndValues.get(i + 1).getKey();
break;
}
}
return right;
}
private void delKeyAndValue(List<KeyAndValue> keyAndValues, int key) {
for (KeyAndValue keyAndValue : keyAndValues) {
if (keyAndValue.getKey() == key) {
keyAndValues.remove(keyAndValue);
break;
}
}
}
//找到node的键值对中在min和max中的键值对
private KeyAndValue getKeyAndValueinMinAndMax(Node node, int min, int max) {
if (node == null) {
return null;
}
List<KeyAndValue> keyAndValues = node.getKeyAndValue();
KeyAndValue keyAndValue = null;
for (KeyAndValue k : keyAndValues) {
if (k.getKey() > min && k.getKey() <= max) {
keyAndValue = k;
break;
}
}
return keyAndValue;
}
private KeyAndValue getMinKeyAndValueInChildNode(Node node) {
if (node.getNodes() == null || node.getNodes().isEmpty()) {
return null;
}
List<KeyAndValue> sortKeyAndValues = new ArrayList<>();
List<Node> childNodes = node.getNodes();
for (Node childNode : childNodes) {
List<KeyAndValue> keyAndValues = childNode.getKeyAndValue();
KeyAndValue minKeyAndValue = keyAndValues.get(0);
sortKeyAndValues.add(minKeyAndValue);
}
Collections.sort(sortKeyAndValues);
return sortKeyAndValues.get(0);
}
private boolean isNeedMerge(Node node) {
if (node == null) {
return false;
}
List<KeyAndValue> keyAndValues = node.getKeyAndValue();
return keyAndValues.size() < rank / 2;
}
//判断一个节点是否有富余的键值对
private boolean isMoreElement(Node node) {
return node != null && (node.getKeyAndValue().size() > rank / 2);
}
}
4.
package com.sbxBase.testBTree;
public class TestMain {
public static void main(String[] args) {
Btree btree = new Btree(4);//初始化了B+树的阶数
btree.insert( new KeyAndValue(1, "123"));
btree.insert(new KeyAndValue(6, "123"));
btree.insert(new KeyAndValue(10, "123"));
btree.insert(new KeyAndValue(2, "123"));
btree.insert(new KeyAndValue(8, "546"));
btree.insert(new KeyAndValue(11, "123"));
btree.insert(new KeyAndValue(18, "12345"));
btree.insert(new KeyAndValue(3, "123"));
btree.insert(new KeyAndValue(15, "12345"));
btree.insert(new KeyAndValue(17, "12345"));
btree.insert(new KeyAndValue(12, "123"));
btree.insert(new KeyAndValue(13, "123"));
btree.insert(new KeyAndValue(4, "123"));
btree.insert(new KeyAndValue(9, "123"));
btree.insert(new KeyAndValue(19, "12345"));
btree.insert(new KeyAndValue(16, "12345"));
btree.insert(new KeyAndValue(5, "123"));
btree.insert(new KeyAndValue(20, "12345"));
btree.insert(new KeyAndValue(7, "12300"));
btree.insert(new KeyAndValue(21, "12345"));
btree.printBtree(btree.getRoot());
btree.delete(1);
btree.printBtree(btree.getRoot());
btree.delete(0);
btree.printBtree(btree.getRoot());
btree.delete(2);
btree.printBtree(btree.getRoot());
btree.delete(11);
btree.printBtree(btree.getRoot());
}
}