数据结构相对来说比较枯燥, 我尽量用最易懂的话,来把B树讲清楚。
学过数据结构的人都接触过一个概念二叉树,简单来说,就是每个父节点最多有两个子节点。
为了在二叉树上更快的进行元素的查找,人们通过不断的改进,从而设计出平衡二叉查找树,也就是这个样子:

平衡二叉查找树的特性由于不是本文的重点,这里就不再展开了。值得一提的是平衡二叉查找树已经基本满足了我们平常的软件开发需求了。但是对于一些需要持久化数据并且支持查询的业务来说,平衡二叉查找树存在一个明显的问题:
如果数据已经持久化到硬盘里边,而我们又想要查询数据的话,我们需要把数据先加载到内存里边再进行比较。
但是,想一想你是不是没法直接判断硬盘里边包含某一段关键字?
如果想要判断,必须要先把数据读到内存里边才可以。如果数据量小的话,这种加载硬盘数据的性能损耗基本可以忽略掉,可是如果数据量大的话,你总不能一次把全部数据加载到内存中再计算。即使你能等,内存也支撑不住。所以我们的办法就是分段查找,一段一段的取到内存里边进行比较,可是这样无论是取多大,怎么比较,又是一个问题。而且更要命的是,倘若过于频繁的一段段从硬盘中取数据的话,浪费在读取数据的性能实在让人可惜。
基于种种原因,于是有人对平衡二叉查找树提出了改良:
1970年Rudolf Bayer,Edward M. McCreight 首次在论文中提到了一种新型的树,并且称之为B树,意味balance tree 平衡树,也称之为 B-树(千万不可称之为B减树哦),B_树等。
其实原理很简单,节点不再是二叉查找树那样的只保存一个关键字,而是保存了多个关键字。这些关键字按照顺序排好。然后还是按照左边当前节点中的关键字都小,右边比当前节点中的数据都大的形式,进行扩展。简单来看,就是这个样子了:

接着为了增加子节点继续扩展的能力,允许一个节点可以多叉,但是依赖的原则还是基本不变的:每一个节点(更准确的说法是关键字)的左分叉要比当前节点的数字小,右分叉要比当前节点数字大。
所以我们基本可以理解为
B树=节点从单一关键字扩展成多关键字+二叉扩展为多叉。到这里,我们基本就算是搞懂B树是什么样子了。
试想一下,如果是这个样子的话,我们的程序就可以先把数据按照节点为单位,一次读取若干个关键字到内存中。然后在内存中进行比较,接着确定好目标所在的下一个分叉,然后获取下一个分叉节点的数据。大概是下边这个样子:


但是出于更严格要求,B树的定义要复杂的多。
首先我们要明白一个词:阶 degree
这个词用来描述一个节点能包含的最大关键字的孩子的个数,也就是说节点最多有多少个分叉,而节点能装的关键字的个数,就是分叉树-1.
注意这个阶是不随着节点关键字的增加和减少来改变的,而是最初定义的一个属性。节点增加关键字和减少关键字都不会改变这个树最初定义的阶的。
接下来围绕这个阶我们设定一些规则,保证B树增加和减少关键字后,整个树仍然是高效可用的。
(1) 树中每个节点最多有m个孩子
直白的说:每个节点最多有m个分叉
(2) 除去根节点这叶子节点外,其它节点至少有m/2个孩子
(3) 根节点至少有2个孩子
直白的说:如果是树中间的节点(非根非叶子),那么每个节点至少都有一半的分叉有孩子,如果是根节点那么就最少有2个孩子
(4) 所有叶节点在同一层,B树的叶节点可以看成是一种外部节点,不包含任何信息
直白的说:所有的叶节点都和高度最高的叶节点呢,画在一个水平线上,这些叶子节点呢,是用来记录外部信息的。可以用空指针表示,代表查找失败到达的位置。
(5) 有k个关键字(注意节点中的关键字要排好顺序)的非叶节点恰好有k+1个孩子。
直白的说:1、节点中的关键字排好顺序,这样方便我们查找
2、有k个关键字就要有k+1个分叉(孩子)
如下图,就是一个多层的B树了,但是要注意,这棵B树画的并不标准,最下层的节点并非叶子,叶子节点是基于这一层节点作为父节点的子节点,在图中叶子节点没有被画出来。(参考第四条)


接下来基于这棵B树,我们举个例子,来查找17这个数字:
第一步:内存加载根节点13,我们比较发现17>13,找13的右侧分叉节点(15,20)
第二步:内存加载节点(15,20),我们比较15,发现 17>15,再比较20,发现17<20,于是取出15的右侧分叉节点(16,17)
第三步:内存加载节点(16,17),我们比较16,发现17>16,再比较17,发现17=17,发现命中,取出17所对应的数据。
我们再举个例子,来查找18这个数字:
前两步都相同
第三步:内存加载节点(16,17),我们比较16,发现18>16,再比较17,发现18>17,于是我们要找17右侧的分叉,但是此时右侧的叶子节点为空(17的右侧分叉对应叶子节点,叶子节点为空),所以我们断定,18不存在。
注意无论是否存在,我们最多都只用了3次内存加载,就完成了比较查找。
这里要特别提下,为啥我们只看重内存加载的速度,而忽略比较次数的耗时呢?这是因为我们在分析性能问题时,需要着重性能的瓶颈来分析。磁盘的读取和内存的访问接近有5个数量级的差异(单位大概是10毫秒与50微秒的差距)。因此我们在这里比较性能时,就是要看进行了多少次磁盘的读取(磁盘的IO),并且主要以减少磁盘IO的手段来提升性能。

当然为了提升比较次数,我们还可以采用二分查找的方式,来判断节点中是否包含某个关键字,进一步加快速度。
接下来影响提升整个IO次数的瓶颈就出现在,一个节点到底能存储多少个关键字,如果关键字存储的越多,我们一次加载到内存中的数据也就越多。同时也要注意,这个关键字的个数不能设置成无限大,因为内存不足以支撑一次加载太多的数据。
基于以上种种,我们可以发现,B树是基于传统硬盘与内存之间的IO差距,而专门设计出来的数据结构,他天然就适用于文件系统。
而对于B树的升级版B+树(B plus tree),我会在接下来的文章中专门讲讲,它又有什么不一样的地方。

数据库索引的基石----B树的更多相关文章

  1. 为什么MySQL数据库索引选择使用B+树?

    在进一步分析为什么MySQL数据库索引选择使用B+树之前,我相信很多小伙伴对数据结构中的树还是有些许模糊的,因此我们由浅入深一步步探讨树的演进过程,在一步步引出B树以及为什么MySQL数据库索引选择使 ...

  2. 数据库索引数据结构总结——ART树就是前缀树

    数据库索引数据结构总结 from:https://zhewuzhou.github.io/2018/10/18/Database-Indexes/ 摘要 数据库索引是数据库中最重要的组成部分,而索引的 ...

  3. B树和B+树对比,为什么MySQL数据库索引选择使用B+树?

    一 基础知识 二叉树 根节点,第一层的节点 叶子节点,没有子节点的节点. 非叶子节点,有子节点的节点,根节点也是非叶子节点. B树 B树的节点为关键字和相应的数据(索引等) B+树 B+树是B树的一个 ...

  4. 数据库索引B-树和B+树

    一开始学习数据结构的时候,主要学习的是数组,队列,链表,队列,栈,树这些数据结构,其中树主要学习二叉树,平衡二叉树,二叉搜索树等这些子节点最多只有两个的树结构.但是,当我们接触数据库的时候,你会发现数 ...

  5. 数据库索引的数据结构b+树

    b+树的查找过程:如上图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,             ...

  6. 为什么说B+-tree比B 树更适合实际应用中操作系统的文件索引和数据库索引?

    B树: B+树 1) B+-tree的磁盘读写代价更低 B+-tree的内部结点并没有指向关键字具体信息的指针.因此其内部结点相对B 树更小.如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所 ...

  7. MySQL数据库索引之B+树

    一.B+树是什么 B+ 树是一种树型数据结构,通常用于数据库和操作系统的文件系统中.B+ 树的特点是能够保持数据稳定有序,其插入与修改操作拥有较稳定的对数时间复杂度.B+ 树元素自底向上插入,这与二叉 ...

  8. 深入理解数据库索引采用B树和B+树的原因

    前面几篇关于数据库底层磁盘文件读取,数据库索引实现细节进行了深入的研究,但是没有串联起来的讲解为什么数据库索引会采用B树和B+树而不是其他的数据结构,例如平衡二叉树.链表等,因此,本文打算从数据库文件 ...

  9. 数据库索引 B+树

    问题1.数据库为什么要设计索引?索引类似书本目录,用于提升数据库查找速度.问题2.哈希(hash)比树(tree)更快,索引结构为什么要设计成树型?加快查找速度的数据结构,常见的有两类:(1)哈希,例 ...

随机推荐

  1. 第12.6节 Python标准库其他内置模块导览

    一. 文本处理服务 string模块 : 常见的字符串操作 difflib模块: 计算差异的辅助工具 textwrap模块: 文本自动换行与填充,能够格式化文本段落,以适应给定的屏幕宽度: unico ...

  2. Python中高级知识(非专题部分)学习随笔

    Python学习随笔:使用xlwings读取和操作Execl文件 Python学习随笔:使用xlwings新建Execl文件和sheet的方法 博客地址:https://blog.csdn.net/L ...

  3. PyQt(Python+Qt)学习随笔:Qt Designer中部件的三个属性sizeHint缺省尺寸、minimumSizeHint建议最小尺寸和minimumSize最小尺寸

    在Qt Designer中的每个部件,要调整部件大小,需要关注三个部件大小相关的属性:sizeHint.minimumSizeHint.minimumSize: 1.sizeHint:为布局管理器中部 ...

  4. flask实现分类搜索的小测试

    最新学长要求实现一个搜索的功能呢,也费了一点功夫.这个案例也没有学长写的好,比学长的实现差了不少,待我仔细研究习再发出相应代码 项目要求,搜索语法如下: titile: xxx #搜索titile的所 ...

  5. SpringCloud-服务间通信方式

    接下来在整个微服务架构中,我们比较关心的就是服务间的服务改如何调用,有哪些调用方式? 总结:在springcloud中服务间调用方式主要是使用 http restful方式进行服务间调用 1. 基于R ...

  6. 团队作业4-Day4

    团队作业4-Day4 项目git地址 1. 站立式会议 2. 项目燃尽图 3. 适当的项目截图 4. 代码/文档签入记录(部分) 5. 每人每日总结 吴梓华:完成了排位模式与练习模式的界面实现,整合代 ...

  7. js 转为整数之Number()、parseInt()、parseFloat()区别

    一:Number() 如果是Boolean值,true和false值将分别被转换为1和0. 如果是数字值,只是简单的传入和返回. 如果是null值,返回0. 如果是undefined,返回NaN. 如 ...

  8. AcWing 406. 放置机器人

    大型补档计划 题目链接 预处理每个列.行连续块. 每个每个列行只能在一个位置匹配,否则冲突. 符合二分图性质,跑匈牙利即可. 点数最坏情况 \(N * M\) (墙空地相间分布),边数最坏情况 \(N ...

  9. 廖雪峰官网学习js 数据类型和变量

    数据类型: number 不分整数 和浮点数 字符串 用' '      "  "   表示 布尔值  true  false && 与运算符(都ture才ture ...

  10. Docker安装rocketmq踩坑指南

    Docker 网络 Docker容器运行的时候有host.bridge.none三种网络可供配置. 默认是bridge,即桥接网络,以桥接模式连接到宿主机:host是宿主网络,即与宿主机共用网络:no ...