B 樹是一種自平衡的查詢樹,能夠保持資料有序。這種資料結構能夠讓查詢資料、順序存取、插入資料及刪除資料的動作,都能在對數時間內完成。
同一般的二叉查詢樹不同,B 樹是一棵多路平衡查詢樹,其特性是:結點的孩子結點數可以多於兩個,且每一個結點處可以儲存多個元素。
在 B 樹中,非葉子結點可以擁有可變數量的子結點,為了維持在預先設定的數量範圍內,通常是對非葉子結點進行合併和分離。其優勢是不需要像其他自平衡查詢樹那樣頻繁地重新保持平衡,其劣勢是結點未被完全填充時會浪費一些空間。
通常,我們會在 B 樹的名稱前新增階數以示說明,如 m 階 B 樹。一個 m 階的 B 樹具有以下特性:
下述展示的是一個 3 階 B 樹:
B 樹可以指一個特定的樹形結構,也可以指大體上的一類樹形結構。
對於 B 樹這一類樹形結構,還包括了 B+ 樹和 B* 樹等結構,它們的簡單定義如下:
對於 B+ 樹,關鍵字只儲存在葉子結點,非葉子結點儲存的是葉子結點所儲存關鍵字的部分拷貝,所有的葉子結點也都在相同的高度,葉子結點本身按關鍵字大小從小到大連結。
B* 樹是 B+ 樹的變體,在 B+ 樹的基礎上,非葉子結點(除根結點外)會增加指向同一層兄弟的指標,且非葉子結點關鍵字個數至少為 \(\frac{2m}{3}\),即塊的最低使用率為 \(\frac{2}{3}\)(B+ 樹為 \(\frac{1}{2}\))。
下面為 B* 樹的結構:
其實,B 樹就是一種為磁碟而設計的樹形結構,主要是降低其他樹形結構存取磁碟的 IO 次數。
從磁碟讀取資料的時間主要涉及到「尋道時間」和「旋轉延遲」:
磁碟的順序讀寫會比隨機讀寫快也是這個原因,在順序讀寫時,磁頭不需要再做尋道,僅需很少的旋轉時間,而隨機讀寫則需要不停地移動磁頭尋找對應的磁軌。
為了儘量減少 IO 操作,計算機系統一般採取預讀的方式,預讀的長度一般為頁(Page)的整數倍。
頁是計算機管理記憶體的邏輯塊,硬體及作業系統往往將主記憶體和磁碟儲存區分割為連續的大小相等的塊,每個儲存塊稱為一頁(多數作業系統頁的大小為 4k),主記憶體和磁碟以頁為單位交換資料。
計算機系統是分頁讀取和儲存的,每次讀取和存取的最小單元為一頁,而磁碟預讀時通常會讀取頁的整數倍。
對於檔案系統和資料庫系統的索引,通常以檔案的形式儲存在磁碟上,因此查詢索引也會執行磁碟 IO 操作,如果查詢過程中磁碟 IO 的存取次數過多會影響索引的效率。
資料庫系統普遍使用 B 樹或者 B+ 樹作為索引結構,其巧妙地利用了磁碟預讀原理,將一個結點設定為一個頁的大小,這樣每個結點只需要一次 IO 就可以完全載入。
同時,在使用過程中還運用了以下技巧:
使用 B 樹作為索引結構時,由於結點的大小等於一個頁的大小,通常階會比較大,因此樹的深度較淺(通常不超過 3),查詢效率非常高。
雖然資料庫系統普遍使用 B 樹作為索引結構,但是仍然有以下缺點:
B 樹所有的插入過程都以根結點起始,首先是要查詢到新元素所要儲存的結點,然後判斷插入結點的元素數量:
如果結點儲存的元素數量小於最大值,那麼有空間容納新的元素,直接插入並保持結點內部有序即可;
如果結點儲存的元素數量大於等於最大值,將它平均地分裂成 2 個結點:
刪除 B 樹中的結點有兩種常用的策略:
對於前一種刪除策略,其刪除流程如下:
在刪除結點中,使 B 樹重新平衡主要會有以下情況:
如果缺少元素結點的右兄弟結點存在且擁有多餘的元素,那麼向左旋轉:
如果缺少元素結點的左兄弟結點存在且擁有多餘的元素,那麼向右旋轉:
如果缺少元素結點的兩個直接兄弟結點都只有最小數量的元素,那麼將它與左兄弟結點以及它們在父結點中的分隔值合併:
對 B 樹做刪除元素的操作比較複雜,但仍然是以保持 B 樹平衡為主,並且不使其導致特性失效。