数据结构相对来讲比较枯燥, 我尽可能用最易懂的话,来把B树讲清楚。
学过数据结构的人都接触过一个概念----二叉树。简单来讲,就是每一个父节点最多有两个子节点。
为了在二叉树上更快的进行元素的查找,人们经过不断的改进,从而设计出一种高效搜索的树----平衡二叉查找树,也就是这个样子:数据结构
平衡二叉查找树的特性因为不是本文的重点,这里就再也不展开了。值得一提的是平衡二叉查找树已经基本知足了咱们日常的软件开发需求了。可是对于一些须要持久化数据而且支持查询的业务来讲,平衡二叉查找树存在一个明显的问题:
若是数据已经持久化到硬盘里边,而咱们又想要查询数据的话,咱们是须要先把数据先加载到内存里边,再进行比较。
想想你是否是无法直接判断硬盘里边包含某一段关键字?
若是想要判断,必需要先把数据读到内存里边才能够。若是数据量小的话,这种加载硬盘数据的性能损耗基本能够忽略掉,却是也没什么影响。但是若是数据量大的话,你总不能一次把所有数据加载到内存中再计算。即便你能等,内存也支撑不住。因此咱们的办法就是分段查找,一段一段的取到内存里边进行比较,但是这样不管是取多大,怎么比较,又是一个问题。并且更要命的是,假若过于频繁的一段段从硬盘中取数据的话,浪费在读取数据的性能实在让人惋惜。性能
基于种种缘由,因而有人对平衡二叉查找树提出了改良:
1970年Rudolf Bayer,Edward M. McCreight 首次在论文中提到了一种新型的树,而且称之为B树,意味balance tree 平衡树,也称之为 B-树(千万不可称之为B减树哦),B_树等。
其实原理很简单,节点再也不是二叉查找树那样的只保存一个关键字,而是保存了多个关键字。这些关键字按照顺序排好。而后仍是按照左边当前节点中的关键字都小,右边比当前节点中的数据都大的形式,进行扩展。简单来看,就是这个样子了:优化
接着为了增长子节点继续扩展的能力,容许一个节点能够多叉,可是依赖的原则仍是基本不变的:每个节点(更准确的说法是关键字)的左分叉要比当前节点的数字小,右分叉要比当前节点数字大。
因此咱们基本能够理解为B树是经过平衡二叉树演化而来:spa
到这里,咱们基本就算是搞懂B树大概是长什么样子了。
试想一下,若是是这个样子的话,咱们的程序就能够先把数据按照节点为单位,一次读取若干个关键字到内存中。(防盗链接:本文首发自http://www.cnblogs.com/jilodream/ )。
而后在内存中进行比较,接着肯定好目标所在的下一个分叉,而后获取下一个分叉节点的数据。大概是下边这个样子:设计
可是出于更严格要求,B树的定义要复杂的多:指针
首先咱们要明白一个词:阶 degree(注意这个概念很是重要)
这个词用来描述一个节点能包含的最大关键字的孩子的个数,也就是说节点最多有多少个分叉,而节点能装的关键字的个数,就是分叉树-1.
注意这个阶是不随着节点关键字的增长和减小来改变的,而是最初定义的一个属性。节点增长关键字和减小关键字都不会改变这个树最初定义的阶的。
接下来围绕这个阶咱们设定一些规则,保证B树增长和减小关键字后,整个树仍然是高效可用的。
(1) 树中每一个节点最多有m个孩子
直白的说:每一个节点最多有m个分叉
(2) 除去根节点这叶子节点外,其它节点至少有m/2个孩子
(3) 根节点至少有2个孩子
直白的说:若是是树中间的节点(非根非叶子),那么每一个节点至少都有一半的分叉有孩子,若是是根节点那么就最少有2个孩子
(4) 全部叶节点在同一层,B树的叶节点能够当作是一种外部节点,不包含任何信息
直白的说:全部的叶节点都和高度最高的叶节点呢,画在一个水平线上,这些叶子节点呢,是用来记录外部信息的。能够用空指针表示,表明查找失败到达的位置。
(5) 有k个关键字(注意节点中的关键字要排好顺序)的非叶节点刚好有k+1个孩子。
直白的说:一、节点中的关键字排好顺序,这样方便咱们查找
二、有k个关键字就要有k+1个分叉(孩子)
以下图,就是一个多层的B树了,可是要注意,这棵B树画的并不标准,最下层的节点并不是叶子,叶子节点是基于这一层节点做为父节点的子节点,在图中叶子节点没有被画出来。(参考第四条)
blog
接下来基于这棵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次内存加载,就完成了比较查找。
这里要特别提下,为啥咱们只看重内存加载的速度,而忽略比较次数的耗时呢?(防盗链接:本文首发自http://www.cnblogs.com/jilodream/ )这是由于咱们在分析性能问题时,须要着重性能的瓶颈来分析。磁盘的读取和内存的访问接近有5个数量级的差别(单位大概是10毫秒与50微秒的差距)。所以咱们在这里比较性能时,就是要看进行了多少次磁盘的读取(磁盘的IO),而且主要以减小磁盘IO的手段来提高性能。内存
固然为了优化比较次数,咱们还能够采用二分查找的方式,来判断节点中是否包含某个关键字,进一步加快速度。
接下来影响提高整个IO次数的瓶颈就出如今,一个节点到底能存储多少个关键字,若是关键字存储的越多,咱们一次加载到内存中的数据也就越多。同时也要注意,这个关键字的个数不能设置成无限大,由于内存不足以支撑一次加载太多的数据。
基于以上种种,咱们能够发现,B树是基于传统硬盘与内存之间的IO差距,而专门设计出来的数据结构,他自然就适用于文件系统。
而对于B树的升级版B+树(B plus tree),我会在接下来的文章中专门讲讲,它又有什么不同的地方。开发