B树、B+树

针对大量的数据存储,用二叉树还是B树?

前一节我们有谈过二叉树,其中查询效率最高的为平衡二叉树,既然如此,用它作为数据库存储引擎是否最合适呢?答案是否定的。原因如下:

  1. 对于大量的存储数据而言,平衡二叉树的树高会很大,很明显的一个缺点,就是树越高,查询效率越低(拿一个树高为3和树高为1000的两棵树对比,答案显而易见);
  2. 针对平衡二叉树而言,逻辑上相邻的节点(比如父子节点),物理上不一定相邻,这就会导致在遍历树的时候,进行频繁的IO操作,而这会严重降低查询的效率。

原因2中涉及到局部性原理,我们再来了解下局部性原理这个概念:
在磁盘读取的场景下,一个数据被用到,它周围的数据被用到的可能性也很大。磁盘存储的最小单位为页(每页默认为4K),所以磁盘读取的过程中,会采取预读取的策略,比如即使只要1字节的数据,也会把该数据所在的页的所有数据读取到,以备后用。

鉴于平衡二叉树的优势及劣势,能否进行改良,以便进行大规模数据的遍历?比如平衡多叉树?是的,就是它,学名B-Tree,也叫B树(程序员心中的逼数~)

B树原理

B-Tree,俗名:B树,别名:B-树、B减树。它是一种平衡多路查找树(不是二路哦~),先看下它的结构:
B树结构图
一颗m阶的B树特性如下:

  1. 树中每个节点最多含有m个孩子;
  2. 除根节点和叶子节点外,它每个节点至少有ceil(m/2)个子节点;
  3. 若根节点不是叶子节点,则至少有2个子节点;
  4. 每个非叶子节点包含n个关键字信息:(n, p0,p1,……,pn, k1,k2, ……,kn),其中:
    • k为关键字,且关键字升序排序
    • p为指向子节点的指针
    • 有n个关键字的父节点,拥有n+1个子节点(类似于n个数划分成了n+1个数据范围)
  5. 每个节点的关键字,都记录着对应的数据在磁盘中的位置,即数据在每个节点上。

上图的结构,以根节点中的关键字17为例说明下含义:17表示一个磁盘文件的文件名(类比数据库中主键id为17的这条记录);小红方块表示这个17文件内容在硬盘中的存储位置(该记录在硬盘中的物理位置);p1表示指向17左子树的指针。磁盘块1代表根节点,磁盘块2代表根节点的左子节点,……,大家会发现,每个节点一个磁盘块,也叫磁盘页,因为磁盘读取是采取预读取的策略,即按页为最小单位读取,即使只需要该页中1字节的数据,也会把整个页的数据加载进来,这样在进行节点中关键字比较的时候,1个节点只需1次IO操作,有效减少了磁盘IO,提高了查询的效率。(二叉树因为逻辑上相邻的节点,物理上不一定相邻,所以需要多次IO)

我们再看下节点的结构,如下图:
节点结构

B+树的原理

B+树是B树的一种变体,也是一个多路平衡查找树,与B树的区别如下:

  1. 每个节点最多含有m个关键字;
  2. 所有的叶子节点包含了全部的关键字信息,且叶子节点本身按照关键字顺序相连;(B树的叶子节点并没有包含全部的关键字)
  3. 所有非叶子节点可以看成索引部分,节点中含有其子节点中最大(最小)关键字;(B树中子节点不包含父节点的关键字)
  4. 所有的数据仅存在叶子节点;(B树的每个节点都会存储数据);

大家可以看下B+树的结构图,如下:
B+树

为什么说B+树比B树更适合做文件索引或数据库索引?

  1. B+树的磁盘读写代价更低。B+树的每个节点并不存储关键字的指针信息,因此节点相比较与B树更小。如果把所有节点放在盘块儿中,那么1个盘块儿包含更多的关键字,相对来说IO的读写次数也就降低了。
     举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶B-tree(一个结点最多8个关键字)的内部结点需要2个盘快。而B+ 树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B 树就比B+ 树多一次盘块查找时间(在磁盘中就是盘片旋转的时间)。
  2. B+树查询效率更加稳定。任何关键字的查找必须走一条从根节点到叶子节点的路径,所有关键字的查询路径相同,所以查询效率相当。
  3. B+树叶子节点的链表结构,更方便范围查询。只需要遍历叶子节点,就可以实现整颗树的遍历。而数据库中基于范围的查询是很频繁的,B树在这种查询场景下,效率低下。

B树的插入操作

插入一个元素时,首先判断在B树中是否存在,如果存在,则结束,否则先在叶子节点中插入新元素。如果叶子节点空间足够,则需要向右移动大于新元素的旧元素;如果空间满了以至没有空间添加新元素,则节点进行分裂,将一半数量的元素移动到右侧的新节点中,同时将中间元素移动到父节点中,(当然,如果父结点空间满了,也同样需要“分裂”操作),而且当结点中关键元素向右移动了,相关的指针也需要向右移。如果在根结点插入新元素,空间满了,则进行分裂操作,这样原来的根结点中的中间关键字元素向上移动到新的根结点中,因此导致树的高度增加一层。如下图所示:
插入逻辑
具体的case可以参考:https://blog.youkuaiyun.com/v_july_v/article/details/6530142,写的非常详细!

### B与B+的概念 B是一种自平衡的多路查找,其设计目的是为了减少磁盘I/O操作次数。它通过确保所有叶子节点处于同一层来维持平衡性[^1]。B+是B的一种变体,保留了B的基本特性,同时在结构上进行了改进以优化范围查询顺序访问性能[^3]。 ### B与B+的区别 1. **节点存储结构** 在B中,每个节点既可以存储数据(键值对),也可以作为索引使用。而在B+中,内部节点仅存储索引信息,所有数据记录都存储在叶子节点中[^2]。 2. **叶子节点链表** B+的所有叶子节点通过指针链接形成一个有序链表,这使得范围查询顺序扫描更加高效。而B不具备这样的特性,导致其在范围查询时效率较低[^3]。 3. **数据分布** B的数据分布在所有节点中,包括非叶子节点。而B+的数据只集中在叶子节点上,这种设计减少了重复存储的可能性,并提高了空间利用率[^1]。 4. **查询效率** 对于单点查询,BB+的表现差异不大。然而,在范围查询场景下,B+由于其叶子节点链表的设计,能够显著提升性能[^2]。 5. **插入与删除操作** B+在插入删除时,由于数据集中存储在叶子节点上,调整操作相对简单,可以避免频繁地调整非叶子节点中的数据。 ### 实现细节 以下是一个简单的B+实现示例,展示了如何构建查询: ```python class BPlusTreeNode: def __init__(self, is_leaf=False): self.keys = [] self.children = [] self.is_leaf = is_leaf self.next_leaf = None # 指向下一个叶子节点 class BPlusTree: def __init__(self, t): self.root = BPlusTreeNode(True) self.t = t def insert(self, key): root = self.root if len(root.keys) == (2 * self.t) - 1: new_node = BPlusTreeNode() self.root = new_node new_node.children.append(root) self.split_child(new_node, 0) self.insert_non_full(new_node, key) else: self.insert_non_full(root, key) def split_child(self, node, index): t = self.t child = node.children[index] new_node = BPlusTreeNode(child.is_leaf) node.children.insert(index + 1, new_node) node.keys.insert(index, child.keys[t - 1]) new_node.keys = child.keys[t:(2 * t) - 1] child.keys = child.keys[0:t - 1] if not child.is_leaf: new_node.children = child.children[t:(2 * t)] child.children = child.children[0:t] def insert_non_full(self, node, key): i = len(node.keys) - 1 if node.is_leaf: node.keys.append(None) while i >= 0 and key < node.keys[i]: node.keys[i + 1] = node.keys[i] i -= 1 node.keys[i + 1] = key else: while i >= 0 and key < node.keys[i]: i -= 1 i += 1 if len(node.children[i].keys) == (2 * self.t) - 1: self.split_child(node, i) if key > node.keys[i]: i += 1 self.insert_non_full(node.children[i], key) ``` ### 应用场景 - **B的应用场景** B适用于需要频繁进行随机访问的场景,例如文件系统中的元数据管理、数据库的索引结构等。由于其支持快速的单点查询,因此在某些特定场景下仍然具有优势[^1]。 - **B+的应用场景** B+广泛应用于数据库系统(如MySQL的InnoDB引擎)文件系统中。它的优点在于能够高效处理范围查询顺序访问,因此非常适合大规模数据集的索引构建[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值