1. 程式人生 > >為什麼檔案儲存要選用B+樹這樣的資料結構?

為什麼檔案儲存要選用B+樹這樣的資料結構?

“檔案儲存要選用B+樹這樣的資料結構”——沒記錯的話,這是嚴蔚敏那本資料結構書上的一句結論。不知道是我沒細看還是她沒細講,反正當時純粹應試地記了這麼個結論。
不求甚解終究不是一個好的學習態度,一直以來我都沒有細想過這個事情,直到看到了這篇博文http://blog.csdn.net/v_JULY_v/article/details/6530142

此文資訊量很大,值得mark下來慢慢精讀。今天就暫記一下關於磁碟檔案儲存選用B+ tree這一點以前沒深究過的問題。畢竟,好記性不如爛筆頭,雖然這篇裡面ctrl-v擔當了比較多的任務……

另一個比較有趣的收穫是終於知道沒有B減樹這個東西了。以前老看到B-樹,以為對應著B+樹,是B樹的某一變種。但實際情況是:

B-樹,即為B樹。因為B樹的原英文名稱為B-tree,而國內很多人喜歡把B-tree譯作B-樹,其實,這是個非常不好的直譯,很容易讓人產生誤解。如人們可能會以為B-樹是一種樹,而B樹又是一種一種樹。而事實上是,B-tree就是指的B樹

下面言歸正傳:

磁碟的構造

磁碟是一個扁平的圓盤(與電唱機的唱片類似)。盤面上有許多稱為磁軌的圓圈,資料就記錄在這些磁軌上。磁碟可以是單片的,也可以是由若干碟片組成的盤組,每一碟片上有兩個面。如下圖11.3中所示的6片盤組為例,除去最頂端和最底端的外側面不儲存資料之外,一共有10個面可以用來儲存資訊。

當磁碟驅動器執行讀/寫功能時。碟片裝在一個主軸上,並繞主軸高速旋轉,當磁軌在讀/寫頭(又叫磁頭) 下通過時,就可以進行資料的讀 / 寫了。

一般磁碟分為固定頭盤(磁頭固定)和活動頭盤。固定頭盤的每一個磁軌上都有獨立的磁頭,它是固定不動的,專門負責這一磁軌上資料的讀/寫。

活動頭盤 (如上圖)的磁頭是可移動的。每一個盤面上只有一個磁頭(磁頭是雙向的,因此正反盤面都能讀寫)。它可以從該面的一個磁軌移動到另一個磁軌。所有磁頭都裝在同一個動臂上,因此不同盤面上的所有磁頭都是同時移動的(行動整齊劃一)。當碟片繞主軸旋轉的時候,磁頭與旋轉的碟片形成一個圓柱體。各個盤面上半徑相同的磁軌組成了一個圓柱面,我們稱為柱面 。因此,柱面的個數也就是盤面上的磁軌數。

磁碟的讀/寫原理和效率

磁碟上資料必須用一個三維地址唯一標示:柱面號、盤面號、塊號(磁軌上的盤塊)。

讀/寫磁碟上某一指定資料需要下面3個步驟:

(1)  首先移動臂根據柱面號使磁頭移動到所需要的柱面上,這一過程被稱為定位或查詢 。

(2)  如上圖11.3中所示的6盤組示意圖中,所有磁頭都定位到了10個盤面的10條磁軌上(磁頭都是雙向的)。這時根據盤面號來確定指定盤面上的磁軌。

(3) 盤面確定以後,碟片開始旋轉,將指定塊號的磁軌段移動至磁頭下。

經過上面三個步驟,指定資料的儲存位置就被找到。這時就可以開始讀/寫操作了。

訪問某一具體資訊,由3部分時間組成:

● 查詢時間(seek time) Ts: 完成上述步驟(1)所需要的時間。這部分時間代價最高,最大可達到0.1s左右。

● 等待時間(latency time) Tl: 完成上述步驟(3)所需要的時間。由於碟片繞主軸旋轉速度很快,一般為7200轉/分(電腦硬碟的效能指標之一, 家用的普通硬碟的轉速一般有5400rpm(筆記本)、7200rpm幾種)。因此一般旋轉一圈大約0.0083s。

● 傳輸時間(transmission time) Tt: 資料通過系統匯流排傳送到記憶體的時間,一般傳輸一個位元組(byte)大概0.02us=2*10^(-8)s

磁碟讀取資料是以盤塊(block)為基本單位的。位於同一盤塊中的所有資料都能被一次性全部讀取出來。而磁碟IO代價主要花費在查詢時間Ts上。因此我們應該儘量將相關資訊存放在同一盤塊,同一磁軌中。或者至少放在同一柱面或相鄰柱面上,以求在讀/寫資訊時儘量減少磁頭來回移動的次數,避免過多的查詢時間Ts。

所以,在大規模資料儲存方面,大量資料儲存在外存磁碟中,而在外存磁碟中讀取/寫入塊(block)中某資料時,首先需要定位到磁碟中的某塊,如何有效地查詢磁碟中的資料,需要一種合理高效的外存資料結構。這種結構可以使得在查詢過程中,IO次數儘量的少。

B- 樹

B 樹又叫平衡多路查詢樹。

B 樹中的每個結點根據實際情況可以包含大量的關鍵字資訊和分支(當然是不能超過磁碟塊的大小,根據磁碟驅動(disk drives)的不同,一般塊的大小在1k~4k左右);這樣樹的深度降低了,這就意味著查詢一個元素只要很少結點從外存磁碟中讀入記憶體,很快訪問到要查詢的資料。相較於2叉樹的優勢就在於此了。

舉個例子,為了簡單,這裡用少量資料構造一棵3叉樹的形式,實際應用中的B樹結點中關鍵字很多的。上面的圖中比如根結點,其中17表示一個磁碟檔案的檔名;小紅方塊表示這個17檔案的內容在硬碟中的儲存位置;p1表示指向17左子樹的指標。

下面,咱們來模擬下查詢檔案29的過程:

    (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,並定位了該檔案記憶體的磁碟地址。

分析上面的過程,發現需要3次磁碟IO操作和3次記憶體查詢操作。關於記憶體中的檔名查詢,由於是一個有序表結構,可以利用折半查詢提高效率。至於3次磁碟IO操作時影響整個B樹查詢效率的決定因素。

當然,如果我們使用平衡二叉樹的磁碟儲存結構來進行查詢,磁碟IO操作最少4次,最多5次。而且檔案越多,B樹比平衡二叉樹所用的磁碟IO操作次數將越少,效率也越高。

B+樹

B+-Tree是應檔案系統所需而產生的一種B-tree的變形樹。

一棵m階的B+樹和m階的B樹的差異在於:

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

2.所有的葉子結點中包含了全部關鍵字的資訊,及指向含有這些關鍵字記錄的指標,且葉子結點本身依關鍵字的大小自小而大的順序連結。 (而B 樹的葉子節點並沒有包括全部需要查詢的資訊)

3.所有的非終端結點可以看成是索引部分,結點中僅含有其子樹根結點中最大(或最小)關鍵字。 (而B 樹的非終節點也包含需要查詢的有效資訊)

為什麼B+樹可以滿足要求?

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

B+-tree的內部結點並沒有指向關鍵字具體資訊的指標。因此其內部結點相對B 樹更小。如果把所有同一內部結點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀入記憶體中的需要查詢的關鍵字也就越多。相對來說IO讀寫次數也就降低了。

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

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

由於非終結點並不是最終指向檔案內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查詢必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當。

B*-Tree

B*-tree是B+-tree的變體,在B樹非根和非葉子結點再增加指向兄弟的指標;B*樹定義了非葉子結點關鍵字個數至少為(2/3)*M,即塊的最低使用率為2/3(代替B+樹的1/2)。給出了一個簡單例項,如下圖所示:

B+樹的分裂:當一個結點滿時,分配一個新的結點,並將原結點中1/2的資料複製到新結點,最後在父結點中增加新結點的指標;B+樹的分裂隻影響原結點和父結點,而不會影響兄弟結點,所以它不需要指向兄弟的指標。

B*樹的分裂:當一個結點滿時,如果它的下一個兄弟結點未滿,那麼將一部分資料移到兄弟結點中,再在原結點插入關鍵字,最後修改父結點中兄弟結點的關鍵字(因為兄弟結點的關鍵字範圍改變了);如果兄弟也滿了,則在原結點與兄弟結點之間增加新結點,並各複製1/3的資料到新結點,最後在父結點增加新結點的指標。

所以,B*樹分配新結點的概率比B+樹要低,空間使用率更高;

總結

通過以上介紹,大致將B樹,B+樹,B*樹總結如下:

B樹:有序陣列+平衡多叉樹;資料存在於非葉子節點上

B+樹:有序陣列連結串列+平衡多叉樹;資料只存在於葉子上。

B*樹:一棵豐滿的B+樹。

走進搜尋引擎的作者樑斌老師針對B樹、B+樹給出了他的意見:

“B+樹還有一個最大的好處,方便掃庫,B樹必須用中序遍歷的方法按序掃庫,而B+樹直接從葉子結點挨個掃一遍就完了,B+樹支援range-query非常方便,而B樹不支援。這是資料庫選用B+樹的最主要原因。

比如要查 5-10之間的,B+樹一把到5這個標記,再一把到10,然後串起來就行了,B樹就非常麻煩。B樹的好處,就是成功查詢特別有利,因為樹的高度總體要比B+樹矮。不成功的情況下,B樹也比B+樹稍稍佔一點點便宜。    B樹比如你的例子中查,17的話,一把就得到結果了。
有很多基於頻率的搜尋是選用B樹,越頻繁query的結點越往根上走,前提是需要對query做統計,而且要對key做一些變化。    另外B樹也好B+樹也好,根或者上面幾層因為被反覆query,所以這幾塊基本都在記憶體中,不會出現讀磁碟IO,一般已啟動的時候,就會主動換入記憶體。”