[LevelDB] 0.3 Btree存儲引擎

B 樹是爲了磁盤或其它存儲設備而設計的一種多叉(下面你會看到,相對於二叉,B樹每一個內結點有多個分支,即多叉)平衡查找樹。
算法

 

B 樹又叫平衡多路查找樹。一棵m階的B 樹 (m叉樹)的特性以下:函數

  1. 樹中每一個結點最多含有m個孩子(m>=2);
  2. 除根結點和葉子結點外,其它每一個結點至少有[ceil(m / 2)]個孩子(其中ceil(x)是一個取上限的函數);
  3. 若根結點不是葉子結點,則至少有2個孩子(特殊狀況:沒有孩子的根結點,即根結點爲葉子結點,整棵樹只有一個根節點);
  4. 全部葉子結點都出如今同一層,葉子結點不包含任何關鍵字信息(能夠看作是外部接點或查詢失敗的接點,實際上這些結點不存在,指向這些結點的指針都爲null);
  5. 每一個非終端結點中包含有n個關鍵字信息: (n,P0,K1,P1,K2,P2,......,Kn,Pn)。其中:
           a)   Ki (i=1...n)爲關鍵字,且關鍵字按順序升序排序K(i-1)< Ki。 
           b)   Pi爲指向子樹根的接點,且指針P(i-1)指向子樹種全部結點的關鍵字均小於Ki,但都大於K(i-1)。 
           c)   關鍵字的個數n必須知足: [ceil(m / 2)-1]<= n <= m-1。

8394323_13074405906V6Q.jpg

 

來模擬下查找文件29的過程:spa

   (1) 根據根結點指針找到文件目錄的根磁盤塊1,將其中的信息導入內存。【磁盤IO操做1次】設計

   (2) 此時內存中有兩個文件名17,35和三個存儲其餘磁盤頁面地址的數據。根據算法咱們發現17<29<35,所以咱們找到指針p2。指針

   (3) 根據p2指針,咱們定位到磁盤塊3,並將其中的信息導入內存。【磁盤IO操做2次】排序

   (4) 此時內存中有兩個文件名26,30和三個存儲其餘磁盤頁面地址的數據。根據算法咱們發現26<29<30,所以咱們找到指針p2。繼承

   (5) 根據p2指針,咱們定位到磁盤塊8,並將其中的信息導入內存。【磁盤IO操做3次】索引

   (6) 此時內存中有兩個文件名28,29。根據算法咱們查找到文件29,並定位了該文件內存的磁盤地址。內存

插入操做get

生 成從空樹開始,逐個插入關鍵字。可是因爲B_樹節點關鍵字必須大於等於[ceil(m/2)-1],因此每次插入一個關鍵字不是在樹中添加一個葉子結點, 而是首先在最底層的某個非終端節點中添加一個「關鍵字」,該結點的關鍵字不超過m-1,則插入完成;不然要產生結點的「分裂」,將一半數量的關鍵字元素分裂到新的其相鄰右結點中,中間關鍵字元素上移到父結點中。

 

一、我們經過一個實例來逐步講解下。插入如下字符字母到一棵空的B 樹中(非根結點關鍵字數小了(小於2個)就合併,大了(超過4個)就分裂):C N G A H E K Q M F W L T Z D P R X Y S,首先,結點空間足夠,4個字母插入相同的結點中,以下圖:

 

8394323_1307525908RHSE.jpg

二、當我們試着插入H時,結點發現空間不夠,以至將其分裂成2個結點,移動中間元素G上移到新的根結點中,在實現過程當中,我們把A和C留在當前結點中,而H和N放置新的其右鄰居結點中。以下圖:

 

8394323_1307525905BTbZ.jpg

三、當我們插入E,K,Q時,不須要任何分裂操做

8394323_1307525899vG17.jpg

 

 

 

 

 

 

 

 

 

 

 

 

四、插入M須要一次分裂,注意M剛好是中間關鍵字元素,以至向上移到父節點中

 

8394323_1307525899h5rR.jpg

五、插入F,W,L,T不須要任何分裂操做

 

8394323_1307525895Gzrw.jpg

六、插入Z時,最右的葉子結點空間滿了,須要進行分裂操做,中間元素T上移到父節點中,注意經過上移中間元素,樹最終仍是保持平衡,分裂結果的結點存在2個關鍵字元素。

 

8394323_13075258952zo1.jpg

七、插入D時,致使最左邊的葉子結點被分裂,D剛好也是中間元素,上移到父節點中,而後字母P,R,X,Y陸續插入不須要任何分裂操做(別忘了,樹中至多5個孩子)。

 

8394323_1307525895q8j1.jpg

八、最後,當插入S時,含有N,P,Q,R的結點須要分裂,把中間元素Q上移到父節點中,可是狀況來了,父節點中空間已經滿了,因此也要進行分裂,將父節點中的中間元素M上移到新造成的根結點中,注意之前在父節點中的第三個指針在修改後包括D和G節點中。這樣具體插入操做的完成。

 

8394323_1307525894XPqO.jpg

 

刪除操做

 

 

首先查找B樹中需刪除的元素,若是該元素在B樹中存在,則將該元素在其結點中進行刪除,若是刪除該元素後,首先判斷該元素是否有左右孩子結點,若是有,則上移孩子結點中的某相近元素到父節點中,而後是移動以後的狀況;若是沒有,直接刪除後,移動以後的狀況。

刪除元素,移動相應元素以後,若是某結點中元素數目(即關鍵字數)小於ceil(m/2)-1,則須要看其某相鄰兄弟結點是否豐滿(結點中元素個數大於ceil(m/2)-1)(還記得第一節中關於B樹的第5個特性中的c點麼?: c)除根結點以外的結點(包括葉子結點)的關鍵字的個數n必須知足:  (ceil(m / 2)-1)<= n <= m-1。m表示最多含有m個孩子,n表示關鍵字數。在本小節中舉的一顆B樹的示例中,關鍵字數n知足:2<=n<=4),若是豐滿,則向父節點借一個元素來知足條件;若是其相鄰兄弟都剛脫貧,即借了以後其結點數目小於ceil(m/2)-1,則該結點與其相鄰的某一兄弟結點進行「合併」成一個結點,以此來知足條件。那我們經過下面實例來詳細瞭解吧。

以上述插入操做構造的一棵5階B樹(樹中最多含有m(m=5)個孩子,所以關鍵字數最小爲ceil(m  / 2)-1=2。仍是這句話,關鍵字數小了(小於2個)就合併,大了(超過4個)就分裂)爲例,依次刪除H,T,R,E。

8394323_1307525894XPqO.jpg

一、首先刪除元素H,固然首先查找H,H在一個葉子結點中,且該葉子結點元素數目3大於最小元素數目ceil(m/2)-1=2,則操做很簡單,我們只須要移動K至原來H的位置,移動L至K的位置(也就是結點中刪除元素後面的元素向前移動)

 

8394323_1307525891j1B1.jpg

二、下一步,刪除T,由於T沒有在葉子結點中,而是在中間結點中找到,我們發現他的繼承者W(字母升序的下個元素),將W上移到T的位置,而後將原包含W的孩子結點中的W進行刪除,這裏剛好刪除W後,該孩子結點中元素個數大於2,無需進行合併操做。

8394323_1307525890gB3b.jpg

三、下一步刪除R,R在葉子結點中,可是該結點中元素數目爲2,刪除致使只有1個元素,已經小於最小元素數目ceil(5/2)-1=2,而由前面咱們已經知道:若是其某個相鄰兄弟結點中比較豐滿(元素個數大於ceil(5/2)-1=2),則能夠向父結點借一個元素,而後將最豐滿的相鄰兄弟結點中上移最後或最前一個元素到父節點中(有沒有看到紅黑樹中左旋操做的影子?),在這個實例中,右相鄰兄弟結點中比較豐滿(3個元素大於2),因此先向父節點借一個元素W下移到該葉子結點中,代替原來S的位置,S前移;而後X在相鄰右兄弟結點中上移到父結點中,最後在相鄰右兄弟結點中刪除X,後面元素前移。

 

8394323_1307525887QhC3.jpg

四、最後一步刪除E, 刪除後會致使不少問題,由於E所在的結點數目恰好達標,恰好知足最小元素個數(ceil(5/2)-1=2),而相鄰的兄弟結點也是一樣的狀況,刪除一個元素都不能知足條件,因此須要該節點與某相鄰兄弟結點進行合併操做;首先移動父結點中的元素(該元素在兩個須要合併的兩個結點元素之間)下移到其子結點中,而後將這兩個結點進行合併成一個結點。因此在該實例中,我們首先將父節點中的元素D下移到已經刪除E而只有F的結點中,而後將含有D和F的結點和含有A,C的相鄰兄弟結點進行合併成一個結點。

 

8394323_1307525886x2r2.jpg

五、也許你認爲這樣刪除操做已經結束了,其實否則,在看看上圖,對於這種特殊狀況,你當即會發現父節點只包含一個元素G,沒達標(由於非根節點包括葉子結點的關鍵字數n必須知足於2=<n<=4,而此處的n=1),這是不可以接受的。若是這個問題結點的相鄰兄弟比較豐滿,則能夠向父結點借一個元素。假設這時右兄弟結點(含有Q,X)有一個以上的元素(Q右邊還有元素),而後我們將M下移到元素不多的子結點中,將Q上移到M的位置,這時,Q的左子樹將變成M的右子樹,也就是含有N,P結點被依附在M的右指針上。因此在這個實例中,我們沒有辦法去借一個元素,只能與兄弟結點進行合併成一個結點,而根結點中的惟一元素M下移到子結點,這樣,樹的高度減小一層。

 

8394323_1307525883hHW0.jpg

爲了進一步詳細討論刪除的狀況,再舉另一個實例:

這裏是一棵不一樣的5序B樹,那我們試着刪除C

 

8394323_1307525877xDde.jpg

因而將刪除元素C的右子結點中的D元素上移到C的位置,可是出現上移元素後,只有一個元素的結點的狀況。

又由於含有E的結點,其相鄰兄弟結點纔剛脫貧(最少元素個數爲2),不可能向父節點借元素,因此只能進行合併操做,因而這裏將含有A,B的左兄弟結點和含有E的結點進行合併成一個結點。

 

8394323_13075258778uzj.jpg

這樣又出現只含有一個元素F結點的狀況,這時,其相鄰的兄弟結點是豐滿的(元素個數爲3>最小元素個數2),這樣就能夠想父結點借元素了,把父結點中的J下移到該結點中,相應的若是結點中J後有元素則前移,而後相鄰兄弟結點中的第一個元素(或者最後一個元素)上移到父節點中,後面的元素(或者前面的元素)前移(或者後移);注意含有K,L的結點之前依附在M的左邊,如今變爲依附在J的右邊。這樣每一個結點都知足B樹結構性質。

 

8394323_1307525873GgD4.jpg

從以上操做可看出:除根結點以外的結點(包括葉子結點)的關鍵字的個數n知足:(ceil(m  / 2)-1)<= n <= m-1,即2<=n<=4。這也佐證了我們以前的觀點。刪除操做完。

 

在B_樹中關鍵字分佈在整個B_樹,而且在上層結點中出現過的關鍵字再也不出如今最底層的結點中。順序鏈中全部的關鍵字不能鏈接在一塊兒。

一顆m階的B+樹和m階的B_樹的差別在於:

 

1.有n棵子樹的結點中含有n個關鍵字; (而B樹是n棵子樹有n-1個關鍵字)

2.全部的葉子結點中包含了所有關鍵字的信息,及指向含有這些關鍵字記錄的指針,且葉子結點自己依關鍵字的大小自小而大的順序連接。(而B樹的葉子節點並無包括所有須要查找的信息)

3.全部的非終端結點能夠當作是索引部分,結點中僅含有其子樹根結點中最大(或最小)關鍵字。 (而B 樹的非終節點也包含須要查找的有效信息)

8394323_1307440587b6WG.jpg

 

1) B+-tree的磁盤讀寫代價更低

B+-tree的內部結點並無指向關鍵字具體信息的指針。所以其內部結點相對B 樹更小。若是把全部同一內部結點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀入內存中的須要查找的關鍵字也就越多。相對來講IO讀寫次數也就下降了。

   舉個例子,假設磁盤中的一個盤塊容納16bytes,而一個關鍵字2bytes,一個關鍵字具體信息指針2bytes。一棵9階B-tree(一個結點最多8個關鍵字)的內部結點須要2個盤快。而B+ 樹內部結點只須要1個盤快。當須要把內部結點讀入內存中的時候,B 樹就比B+ 樹多一次盤塊查找時間(在磁盤中就是盤片旋轉的時間)。

2) B+-tree的查詢效率更加穩定

因爲非終結點並非最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。因此任何關鍵字的查找必須走一條從根結點到葉子結點的路。全部關鍵字查詢的路徑長度相同,致使每個數據的查詢效率至關。

相關文章
相關標籤/搜索