B+Tree及其创建过程

B+Tree

B+树和B树的定义类似,但是B+树的非叶子结点只存储着索引,所有的数据信息都存储在叶子结点。

和B树相比

①B+树的特性使得在一个磁盘区域中存放更多的索引值,因此可
让一次IO操作读取到更多的索引进行定位,减少IO读取磁盘的操作
,从而提升效率。
②由于B+树的叶子结点都存储着下一个叶子结点的信息,所以,并不需要遍历整个树,只需找到最左边的孩子即可输出所有信息。
③对于B树,一般要比B+树矮一些,对于一些频率查找往往使用B树,让频率较大的结点离树根近些,在启动的时候,让上面几层直接进入内存,并不会出现磁盘IO。
④B+树的优势是在磁盘,对于内存并没有优势

一个争论

对于B+树的结构,有两种说法:
①孩子数和key的数目相同
②同B树一样,孩子树比key数多一
Ps:我此处使用的是第二种说法

建立B+树的具体过程

需要三个类来定义相关结点

①BPTree(存储B树,及相关信息)

class BPTree{
 	//最大存储关键字
 	public int max;
 	//最小存储关键字
 	public int min;
 	//跟结点(可能为叶子结点。也可能为索引结点)
 	public Object root;
 }

②IndexNode(索引结点,存储索引)

class IndexNode{
 	//关键字个数
 	public int num;
 	//结点数据
 	public int[] index;
	//结点孩子(可能为索引,也可能为数据)
 	public Object[] child;
 	//父结点(只可能为索引)
 	public IndexNode parent;
 }

③LeafNode(叶子结点,存储索引及数据)

class LeafNode{
 	//关键字个数
 	public int num;
 	//索引
 	public int[] index;
// 	//结点数据和添加索引同步操作
// 	public Data[] data;
 	//父结点
 	public IndexNode parent;
 	//同层下一个叶子结点
 	public LeafNode nextLeaf;
 }

Ps:我之后的操作只是演示B+树的建立,所以未存储数据

新数据寻找位置并插入树中

/**
  * 定位并添加数据
  * @param root 根结点
  * @return 添加数据的结点
  */
 private LeafNode locateData(Object root,int index) {
  	//判断根结点是索引结点还是叶子结点
  	IndexNode iNode = null;
  	LeafNode lNode = null;
  	if(root instanceof IndexNode) {
   		//索引结点,需定位,然后找下一个结点
   		iNode = (IndexNode)root;
   		//定位
   		int locate = 0;
   		for(; locate<iNode.num; locate++) {
    			if(index < iNode.index[locate]) {
     				break;
    			}
   		}
   		return locateData(iNode.child[locate], index);
  	}else if(root instanceof LeafNode) {
   		//叶子结点,可添加数据
   		lNode = (LeafNode)root;
   		//定位
   		int locate = 0;
   		for(; locate<lNode.num; locate++) {
    			if(index < lNode.index[locate]) {
    				break;
    			}
   		}
   		//移动数据
   		for(int j=lNode.num-1; j>= locate; j--) {
    			lNode.index[j+1] = lNode.index[j];
  		}
   		//添加数据
   		lNode.index[locate] = index;
   		lNode.num++;
   		return lNode;
  	}
  	return null;
 }

结点的分裂

由于树中存在两种结点,而这两种结点的分裂方式不太相同
①叶子结点分裂:找到中间值index,新建一个叶子结点,将其(包含自己)右边部分迁移到新结点中去,调整叶子结点的下一位结点地址(先将原结点的下一位赋给新结点,再将新结点作为原结点下一位),再将该结点上升到父结点,原结点作为左孩子,新结点作为右孩子。

/**
  * 分裂叶子结点
  * @param lNode 叶子结点
  */
 private void spiltLeafNode(LeafNode lNode) {
  	int spiltLocate = lNode.num/2;
  	int valueIndex = lNode.index[spiltLocate];
  	IndexNode parent = lNode.parent;
  	//若根结点是叶子结点,则进行更新
  	if(parent == null) {
   		parent = new IndexNode(this.bpTree.max);
   		this.bpTree.root = parent;
   		System.out.print("\t层数加一");
  	}
  	//新建一个叶子结点,将数据分为两部分
  	LeafNode newlNode = new LeafNode(this.bpTree.max);
  	int insert = 0;
  	for(int i = spiltLocate; i < lNode.num; i++) {
   		newlNode.index[insert] = lNode.index[i];
   		lNode.index[i] = 0;
//   		newlNode.data[insert] = lNode.data[i];
//   		lNode.data[i] = null;
   		insert++;
  	}
  	//更新结点相关信息
  	lNode.num = spiltLocate;
  	newlNode.num = insert;
  	lNode.parent = parent;
  	newlNode.parent = parent;
  	//连接叶子结点
  	newlNode.nextLeaf = lNode.nextLeaf;
  	lNode.nextLeaf = newlNode;
  
  	//在父结点中定位索引位置
  	int parlocate = 0;
  	for(; parlocate < parent.num; parlocate++) {
   		if(valueIndex < parent.index[parlocate]) {
    			break;
   		}
 	}
  	//移动数据和孩子
  	for(int i=parent.num-1; i >= parlocate; i--) {
   		parent.index[i+1] = parent.index[i];
   		parent.child[i+2] = parent.child[i+1];
  	}
 	//加入父结点,调整相关结构
  	parent.index[parlocate] = valueIndex;
  	parent.num++;
  	parent.child[parlocate] = lNode;
  	parent.child[parlocate+1] = newlNode;
  	//检测父结点(索引结点)
  	if(parent.num > this.bpTree.max) {
   		System.out.print("\t再次分裂");
   		spiltIndexNode(parent);
  	}
  
  }

②索引结点分裂:找到中间值index,新建一个索引结点,将其(不包含自己)右边部分迁移到新结点中(此处不要忘了迁移孩子及更改孩子的父结点),再将该结点上升到父结点,原结点作为左孩子,新结点作为右孩子。

/**
  * 分裂索引结点
  * @param iNode 索引结点
  */
  private void spiltIndexNode(IndexNode iNode) {
   	int spiltLocate = iNode.num/2;
   	int valueIndex = iNode.index[spiltLocate];
   	IndexNode parent = iNode.parent;
   	//当分裂到根结点
   	if(parent == null) {
    		parent = new IndexNode(this.bpTree.max);
    		this.bpTree.root = parent;
    		System.out.print("\t层数加一");
   	}
   	//新建一个结点,存储分裂的结点
   	IndexNode newInNode = new IndexNode(this.bpTree.max);
   	//跳过中间结点,将后半部分放入新结点中(调整,索引,孩子,孩子结点的父结点)
   	int insert = 0;
   	for(int i = spiltLocate+1; i < iNode.num; i++) {
    		newInNode.index[insert] = iNode.index[i];
    		iNode.index[i] = 0;
    		newInNode.child[insert] = iNode.child[i];
    		iNode.child[i] = null;
    		//此处孩子节点可能为两种对象,需进行转换
    		if(newInNode.child[insert] instanceof LeafNode) {
     			LeafNode midNode = (LeafNode)newInNode.child[insert];
     			midNode.parent = newInNode;
    		}else {
     			IndexNode midNode = (IndexNode)newInNode.child[insert];
     			midNode.parent = newInNode;
    		}
    		insert++;
   	}
   	//调整最后一个孩子
   	newInNode.child[insert] = iNode.child[iNode.num];
   	if(newInNode.child[insert] instanceof LeafNode) {
    		LeafNode midNode = (LeafNode)newInNode.child[insert];
    		midNode.parent = newInNode;
   	}else {
    		IndexNode midNode = (IndexNode)newInNode.child[insert];
    		midNode.parent = newInNode;
   	}
   	iNode.child[iNode.num] = null;
   	//舍去上升到父结点的结点
   	iNode.index[spiltLocate] = 0;
   	newInNode.num = insert;
   	iNode.num = spiltLocate;
   	newInNode.parent = parent;
   	iNode.parent = parent;
   	//定位父结点中的位置
   	int parLocate = 0;
   	for(; parLocate < parent.num; parLocate++) {
   		 if(valueIndex < parent.index[parLocate]) {
     			break;
    		}
   	}
   	//调整父结点索引及孩子
   	for(int i = parent.num-1; i >= parLocate; i--) {
    		parent.index[i+1] = parent.index[i];
    		parent.child[i+2] = parent.child[i+1];
   	}
   	//加入中间数据
   	parent.index[parLocate] = valueIndex;
   	parent.child[parLocate] = iNode;
   	parent.child[parLocate+1] = newInNode;
   	parent.num++;
  	//检测父结点(索引结点)
  	if(parent.num > this.bpTree.max) {
   		System.out.print("\t再次分裂");
   		spiltIndexNode(parent);
  	}
  }

从文件中读取数据,构建B+树

/**
  * 构建一棵B+树
  * @param file 文件名
  * @param m 树的阶树
  */
 public void CreateBPTree(String file,int m) {
  	//先构建根结点
  	if(this.bpTree == null) {
   		bpTree = new BPTree(m);
   		bpTree.root = new LeafNode(bpTree.max);
  	}
  	//读取数据,加入树
  	BufferedReader bReader = null;
  	try {
   		bReader = new BufferedReader(new FileReader(file));
   		String data = null;
   		while((data=bReader.readLine()) != null) {
    			int index = Integer.parseInt(data);
    			//寻找位置,进行插入
    			LeafNode addNode = this.locateData(this.bpTree.root, index);
    			//判断是否需要进行分裂
    			if(addNode != null && addNode.num > this.bpTree.max) {
     				System.out.print("加入"+index+"时,溢出需要分裂");
     				spiltLeafNode(addNode);
     				System.out.println();
    			}
   		}
  	} catch (Exception e) {
   		e.printStackTrace();
  	} finally {
   		try {
    			if(bReader != null) {
    	 			bReader.close();
    			}
   		} catch (Exception e2) {
   		 e2.printStackTrace();
  		}
  	} 
 }

测试

public static void main(String[] args) {
  	BPlusTree bPlusTree = new BPlusTree();
  	bPlusTree.CreateBPTree("Test/BPlusTree.txt", 4);
  	//遍历叶子结点
  	BPTree bpTree = bPlusTree.getBpTree();
  	IndexNode root = (IndexNode)bpTree.root;
  	Object move = root.child[0];
  	while(move != null) {
   		if( move instanceof IndexNode) {
    			IndexNode iNode = (IndexNode)move;
    			move = iNode.child[0];
   		}else {
    			break;
   		}
 	}
  	LeafNode lNode = (LeafNode)move;
  	while(lNode != null) {
   		for(int i=0; i< lNode.num; i++) {
    			System.out.print(lNode.index[i]+" ");
   		}
   		lNode = lNode.nextLeaf;
  	}
 }

结果
①4阶
在这里插入图片描述
②3阶
在这里插入图片描述

发布了82 篇原创文章 · 获赞 1 · 访问量 1466

猜你喜欢

转载自blog.csdn.net/qq_41891805/article/details/104889333