數據庫索引的基石----B樹

數據結構相對來講比較枯燥, 我儘可能用最易懂的話,來把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),我會在接下來的文章中專門講講,它又有什麼不同的地方。
開發

相關文章
相關標籤/搜索