紅黑樹现诀、B樹、B+樹各自適用的場(chǎng)景

1. 磁盤基礎(chǔ)知識(shí)

  • 分頁(yè):

現(xiàn)代操作系統(tǒng)都使用虛擬內(nèi)存來印射到物理內(nèi)存履肃,內(nèi)存大小有限且價(jià)格昂貴仔沿,所以數(shù)據(jù)的持久化是在磁盤上。虛擬內(nèi)存榆浓、物理內(nèi)存于未、磁盤都使用頁(yè)作為內(nèi)存讀取的最小單位。一般一頁(yè)為4KB(8個(gè)扇區(qū),每個(gè)扇區(qū)512B烘浦,8*512B=4KB)抖坪。

  • 局部性原理:
  1. 當(dāng)一個(gè)數(shù)據(jù)被用到時(shí),其附近的數(shù)據(jù)也通常會(huì)馬上被使用闷叉。
  2. 程序運(yùn)行期間所需要的數(shù)據(jù)通常比較集中擦俐。
  • 磁盤預(yù)讀原理:

磁盤讀取依靠的是機(jī)械運(yùn)動(dòng),分為尋道時(shí)間握侧、旋轉(zhuǎn)延遲蚯瞧、傳輸時(shí)間三個(gè)部分,這三個(gè)部分耗時(shí)相加就是一次磁盤IO的時(shí)間品擎,大概 9ms 左右埋合。這個(gè)成本是訪問內(nèi)存的十萬倍左右;

磁盤讀取的速度遠(yuǎn)小于內(nèi)存萄传,所以盡量減少 I/O 次數(shù)是提高效率的關(guān)鍵甚颂。

根據(jù)局部性原理,且由于磁盤順序讀取的效率很高(不需要尋道時(shí)間秀菱,只需很少的旋轉(zhuǎn)時(shí)間)振诬,所以即使只需要讀取一個(gè)字節(jié),磁盤也會(huì)讀取一頁(yè)的數(shù)據(jù)衍菱。即磁盤預(yù)讀時(shí)通常會(huì)讀取頁(yè)的整倍數(shù)赶么。

2. 樹基礎(chǔ)知識(shí)回顧

排序二叉樹:左 < 跟 < 右
B 樹:有序數(shù)組 + 多叉平衡樹,節(jié)點(diǎn)存儲(chǔ)關(guān)鍵字脊串、數(shù)據(jù)辫呻、指針;
B+ 樹:有序數(shù)組鏈表 + 多叉平衡樹洪规,非葉子節(jié)點(diǎn)存儲(chǔ)指針印屁、關(guān)鍵字,不存儲(chǔ)數(shù)據(jù)斩例;
紅黑樹:紅黑樹是一種不大嚴(yán)格的平衡樹(平衡樹要求太高)

平衡樹是為了防止二叉查找樹退化為鏈表雄人,而紅黑樹在維持平衡以確保 O(log2(n)) 的同時(shí),不需要頻繁著調(diào)整樹的結(jié)構(gòu)念赶;

二叉樹的存儲(chǔ)結(jié)構(gòu)

  1. 順序存儲(chǔ)(適用于完全二叉樹)
二叉樹順序存儲(chǔ)

index 之間的對(duì)應(yīng)關(guān)系:

完全二叉樹順序存儲(chǔ)

注意:二叉樹的順序存儲(chǔ)只適合存儲(chǔ)完全二叉樹础钠,否則 index 無法和節(jié)點(diǎn)對(duì)應(yīng)起來,會(huì)有點(diǎn)惡心:

非完全二叉樹順序存儲(chǔ)
  1. 鏈?zhǔn)酱鎯?chǔ)
二叉樹鏈?zhǔn)酱鎯?chǔ)

這里要好好理解一下叉谜,不然會(huì)影響后面的理解旗吁。

3. 為什么不能使用二叉樹來存儲(chǔ)數(shù)據(jù)庫(kù)索引

先說結(jié)論:

  1. 平衡二叉樹進(jìn)行插入/刪除時(shí),大概率需要通過左旋/右旋來維持平衡停局;
  2. 旋轉(zhuǎn)需要加載整個(gè)樹很钓,頻繁旋轉(zhuǎn)效率低香府;
  3. 二叉樹的 I/O 次數(shù)近似為 O(log2(n));
  4. 范圍查詢時(shí)码倦,二叉樹的時(shí)間復(fù)雜度會(huì)退化成 O(n)企孩;
  5. 二叉樹退化成鏈表時(shí),時(shí)間復(fù)雜度也近似退化成了 O(n)袁稽;
  6. 二叉樹無法使用磁盤預(yù)讀功能勿璃;

其實(shí)單論范圍查詢,在關(guān)系型數(shù)據(jù)庫(kù)中就基本沒有使用二叉樹的可能了推汽。但是為了加深對(duì)知識(shí)的了解补疑,來看看其他的原因。

先剔除掉范圍查詢的情況歹撒,原因 1莲组、2、6 可以通過紅黑樹來解決栈妆,那么其實(shí)就剩下 2 個(gè)原因:

  1. I/O 次數(shù)對(duì)比胁编;
  2. 磁盤預(yù)讀功能的利用;

4. 二叉樹的 I/O 次數(shù)分析

先說 I/O 次數(shù):

其實(shí)相比于二叉樹鳞尔,B 樹县匠、B+樹智玻, CPU 的運(yùn)算次數(shù)并沒有變化,甚至增多疗隶。但是 CPU 運(yùn)算次數(shù)相比于 I/O 的消耗而言霞扬,可以忽略不計(jì)糕韧,所以 I/O 次數(shù)是評(píng)價(jià)一個(gè)數(shù)據(jù)庫(kù)索引的效率高低的關(guān)鍵指標(biāo)。

對(duì)于紅黑樹而言喻圃,其 I/O 次數(shù)近似為 log2(n)萤彩,為什么是近似呢?

首先斧拍,索引是存儲(chǔ)在磁盤上的雀扶,磁盤上的數(shù)據(jù)大部分情況下是連續(xù)的,但是隨著增刪改查的發(fā)生肆汹,有可能產(chǎn)生很多碎片愚墓,也就是說:

  • 索引在磁盤上的存儲(chǔ)也不一定是連續(xù)的;

這里昂勉,嚴(yán)謹(jǐn)起見浪册,我們來分兩種情況:

  1. 索引節(jié)點(diǎn),即樹的節(jié)點(diǎn)在磁盤上存儲(chǔ)是連續(xù)岗照;

假設(shè)一個(gè)頁(yè)能存儲(chǔ) 5 個(gè)節(jié)點(diǎn)村象,假設(shè)二叉樹如下:

二叉樹

注意笆环,序號(hào)只代表在磁盤中存儲(chǔ)的順序,不代表對(duì)應(yīng)節(jié)點(diǎn)的關(guān)鍵字的值厚者;

二叉樹可能是鏈?zhǔn)酱鎯?chǔ)咧织,也可能是順序存儲(chǔ)。但是這里假設(shè)節(jié)點(diǎn)在磁盤上的存儲(chǔ)是連續(xù)的籍救,所以這里可以近似理解成順序存儲(chǔ)习绢。即使是鏈?zhǔn)酱鎯?chǔ),無非就是 pNext 指針指向下一個(gè)連續(xù)的內(nèi)存地址而已蝙昙。

現(xiàn)在假設(shè)搜索的結(jié)果是最左邊的葉子節(jié)點(diǎn) 16闪萄,因?yàn)榇疟P預(yù)讀的特性,加上一個(gè)頁(yè)能存儲(chǔ) 5 個(gè)節(jié)點(diǎn)奇颠,第一次 I/O :

第一次I/O

如上败去,第一次 I/O 就讀取了 5 個(gè)節(jié)點(diǎn),不僅把根節(jié)點(diǎn)讀取進(jìn)內(nèi)存了烈拒,還把節(jié)點(diǎn) 2 和 4 都讀取進(jìn)去了圆裕,看上去還節(jié)約了兩次 I/O ?好厲害的樣子......

此時(shí)荆几,會(huì)根據(jù)二分法查找吓妆,對(duì)比 1 號(hào)節(jié)點(diǎn)然后去找節(jié)點(diǎn) 2,緊接著找節(jié)點(diǎn) 4吨铸,因?yàn)檫@兩個(gè)節(jié)點(diǎn)都在內(nèi)存中了行拢,所以不需要進(jìn)行 I/O

這里再說一次,序號(hào)不代表節(jié)點(diǎn)的關(guān)鍵字诞吱,而是單純的表示節(jié)點(diǎn)在磁盤中的排列順序舟奠;

緊接著,會(huì)需要 8 號(hào)節(jié)點(diǎn)房维,而 8 不再內(nèi)存中沼瘫,所以進(jìn)行第二次 I/O 同樣是讀取一頁(yè),即 5 個(gè)節(jié)點(diǎn):

第二次I/O

這次雖然也是讀取了 5 個(gè)節(jié)點(diǎn)咙俩,但是實(shí)際上只有 8 號(hào)節(jié)點(diǎn)有實(shí)際作用耿戚,其他節(jié)點(diǎn)并沒什么卵用(這是二叉樹無法使用預(yù)讀功能的本質(zhì)),但是現(xiàn)在還沒體現(xiàn)出劣勢(shì)暴浦,現(xiàn)在對(duì)比之后需要 16 號(hào)節(jié)點(diǎn)溅话,繼續(xù)第三次讀取:

第三次I/O

此時(shí)找到了 16歌焦,并將結(jié)果返回飞几。

這是高度為 4 的情況,且只有 31 個(gè)數(shù)據(jù)独撇。但是實(shí)際使用中屑墨,怎么可能就 31 個(gè)數(shù)據(jù)躁锁?假設(shè)要找的是 32 號(hào)節(jié)點(diǎn),因?yàn)?16 號(hào)節(jié)點(diǎn)之后的 17-20 雖然被加載進(jìn)內(nèi)存了卵史,但是完全沒用战转。那么就需要再進(jìn)行一次 I/O 來加載 32 號(hào)節(jié)點(diǎn)所在的頁(yè),同時(shí)也會(huì)將 33-36 加載進(jìn)內(nèi)存以躯,但是這些節(jié)點(diǎn)并無卵用槐秧。

如果要找的是 1000 ,10000忧设?

所以刁标,隨著層級(jí)的深入,會(huì)出現(xiàn):

  1. 一個(gè)頁(yè)中只有一個(gè)節(jié)點(diǎn)有用(二分法查找要的是子節(jié)點(diǎn)而不是兄弟節(jié)點(diǎn))址晕;
  2. I/O 次數(shù)近似等于log2(n)膀懈;

即:

  1. 第一次 I/O 可能的優(yōu)勢(shì)在層級(jí)加深之后就沒有了;
  2. 就算是紅黑樹谨垃,也只能將時(shí)間復(fù)雜度維持在 log2(n)启搂;

上述討論的是索引樹在磁盤上的存儲(chǔ)是連續(xù)的,如果不是連續(xù)的刘陶,那么按頁(yè)讀取到的臟數(shù)據(jù)會(huì)更多胳赌,上述的情況中,前幾次 I/O 讀取到有用的數(shù)據(jù)的概率會(huì)變低易核,所以 I/O 的次數(shù)只會(huì)增多而不會(huì)減少,即仍然是近似于 log2(n)匈织。

5. B/B+樹

B 樹即:多路平衡查找樹;

B 樹的巧妙之處在于:

  1. 將一個(gè)節(jié)點(diǎn)的大小設(shè)置為一頁(yè)的大心抵薄;
  2. 一個(gè)節(jié)點(diǎn)可以存放多個(gè)關(guān)鍵字(多叉樹)纳决;
  3. 自平衡碰逸;

這 3 點(diǎn)結(jié)合起來就可以做到:

  1. 一個(gè)節(jié)點(diǎn)大小為一頁(yè),被加載進(jìn)內(nèi)存時(shí)阔加,這些關(guān)鍵字在進(jìn)行對(duì)比饵史,找出需要 leftChild 還是 rightChild 時(shí),都是有用的(如最右側(cè)時(shí)需要對(duì)比所有節(jié)點(diǎn))胜榔;
  2. 一個(gè)節(jié)點(diǎn)可以存儲(chǔ)多個(gè)關(guān)鍵字胳喷,有效降低了樹的高度;

B+ 樹的巧妙之處在于:

  1. 非葉子節(jié)點(diǎn)不存儲(chǔ)數(shù)據(jù)夭织,進(jìn)一步增大了一頁(yè)中存儲(chǔ)關(guān)鍵字的數(shù)量吭露;
  2. 葉子節(jié)點(diǎn)中存儲(chǔ)數(shù)據(jù)且存在指向下一頁(yè)的鏈表指針,可以使用順序查詢(支持范圍查詢)尊惰;

6. B/B+樹的索引數(shù)量

B 樹的節(jié)點(diǎn)中存儲(chǔ):指針讲竿、關(guān)鍵字(主鍵)泥兰、數(shù)據(jù)
B+ 樹的非葉子節(jié)點(diǎn):指針、關(guān)鍵字
B+樹的葉子節(jié)點(diǎn):指針(鏈表)题禀、關(guān)鍵字鞋诗、數(shù)據(jù)

注意,這里不是絕對(duì)的迈嘹,比如有的 B+ 樹中葉子節(jié)點(diǎn)存儲(chǔ)的不是數(shù)據(jù)削彬,而是指向數(shù)據(jù)的指針。查詢到指針之后再去對(duì)應(yīng)地址取出數(shù)據(jù)秀仲,但是這樣應(yīng)該會(huì)增加一次 I/O 吧融痛,應(yīng)該也是在數(shù)據(jù)量和 I/O 次數(shù)之間做了取舍,具體先不討論啄育。

以 Sqlite3.12 之后為例酌心,page_size = 16k,假設(shè)指針為 8 byte挑豌,假設(shè)關(guān)鍵字類型占 8 byte安券,假設(shè)數(shù)據(jù)占 1 KB;

B 樹的一個(gè)節(jié)點(diǎn):

b樹

一頁(yè)能存儲(chǔ)的數(shù)據(jù)量為:16kb / (1KB+8byte+8byte) ≈ 16氓英;

高度為 3 的 B 樹能存儲(chǔ) 16 x16 x16 = 4096 條數(shù)據(jù)

相比于二叉樹的 1 個(gè)而言侯勉,確實(shí)有效降低了樹的層級(jí)。而且上述是假設(shè)數(shù)據(jù)為 1KB铝阐,如果數(shù)據(jù)沒那么大址貌,高度為 3 的 B 樹能存儲(chǔ)更多的數(shù)據(jù),但是如果用在大型數(shù)據(jù)庫(kù)索引上還是不夠徘键。

B+ 樹:

B+樹

如上圖练对,B+樹的核心在于非葉子節(jié)點(diǎn)不存儲(chǔ)數(shù)據(jù)。

這樣做可以減少非葉子結(jié)點(diǎn)占用的空間吹害,增大一頁(yè)所能存儲(chǔ)的數(shù)據(jù)量螟凭,最大程度減少樹的層級(jí)。

仍然是以上假設(shè)它呀,假如樹的高度為 3 螺男,那么就有兩層存儲(chǔ)關(guān)鍵字+指針,一層葉子節(jié)點(diǎn)來存儲(chǔ)實(shí)際數(shù)據(jù)纵穿。

一頁(yè)能存儲(chǔ)的關(guān)鍵字為:16 * 1024 / (8 + 8) = 1024
一頁(yè)能存儲(chǔ)的數(shù)據(jù)量為:16KB / (1KB + 8byte + 8byte) = 16
(這里計(jì)算不完全準(zhǔn)確下隧,實(shí)際情況應(yīng)該是1頁(yè)數(shù)據(jù)中只有一個(gè)鏈表指針指向下一頁(yè))
能存儲(chǔ)的關(guān)鍵字為:1024 * 1024 = 1048576;

因?yàn)槎斯?jié)點(diǎn)又有 1024 個(gè)指針谓媒,這些指針可以指向一個(gè)頁(yè)淆院,頁(yè)中存儲(chǔ)數(shù)據(jù),也就是葉子節(jié)點(diǎn)篙耗,一頁(yè)能存儲(chǔ) 16 個(gè)葉子節(jié)點(diǎn)迫筑,所以總共能索引的數(shù)據(jù)量為 1048576 * 16 ≈ 1600萬宪赶;如果高度為 4 ,則再乘以 1024 約為 17億.....

上述推理中脯燃,理解終端節(jié)點(diǎn)的指針指向一個(gè)頁(yè)搂妻,頁(yè)中存儲(chǔ)著關(guān)鍵字 + 數(shù)據(jù) + 鏈表指針是關(guān)鍵。page 標(biāo)記如下辕棚,有助理解:

理解

雖然葉子節(jié)點(diǎn)很多欲主,一個(gè) page 對(duì)應(yīng)一個(gè)葉子節(jié)點(diǎn)甚至是多個(gè) page 才能存下一個(gè)葉子節(jié)點(diǎn),但是這些是存在磁盤上的逝嚎,找到對(duì)應(yīng)的 page 之后才去加載對(duì)應(yīng)的 page扁瓢。索引超大數(shù)據(jù)量的同時(shí),不會(huì)對(duì) I/O 次數(shù)產(chǎn)生影響补君,這就是這個(gè)設(shè)計(jì)的牛逼之處引几。

但是這樣也是有缺點(diǎn)的:

無論查詢結(jié)果如何,都必須走到葉子節(jié)點(diǎn)才結(jié)束挽铁,也就是 I/O 次數(shù)固定為 O(h) 或者說是 log(n)(底數(shù)為節(jié)點(diǎn)子分支個(gè)數(shù))伟桅,這個(gè) h 一般為 2-3,排除掉根節(jié)點(diǎn)常駐內(nèi)存叽掘,高度為 3 的 B+ 樹進(jìn)行兩次 I/O 就可以索引千萬級(jí)別的數(shù)據(jù),高度為 4 的 B+ 樹楣铁,進(jìn)行 3 次 I/O 就能索引十億級(jí)別的數(shù)據(jù)量,這個(gè)效果還是很好的更扁。

所以盖腕,這個(gè)缺點(diǎn)也可以說成是優(yōu)點(diǎn):穩(wěn)定(穩(wěn)如一條老狗??)

7. 實(shí)際應(yīng)用

  • 紅黑樹優(yōu)點(diǎn)

紅黑樹常用于存儲(chǔ)內(nèi)存中的有序數(shù)據(jù),增刪很快浓镜,內(nèi)存存儲(chǔ)不涉及 I/O 操作溃列。

  • B/B+樹的優(yōu)點(diǎn)

更適合磁盤存儲(chǔ),減少了樹的層級(jí)膛薛,進(jìn)而減少 I/O 次數(shù)哭廉;

  • B 樹和 B+ 樹對(duì)比

都是 B 樹,但是 B+樹更適合范圍查詢相叁,比如 Mysql,且查詢次數(shù)很穩(wěn)定辽幌,為 logn增淹。而 B 樹更適合鍵值對(duì)型的聚合數(shù)據(jù)庫(kù),比如 MongoDB乌企,查詢次數(shù)最優(yōu)為 O(1)虑润;

紅黑樹更適合內(nèi)存存儲(chǔ),B 樹更適合鍵值對(duì)存儲(chǔ)加酵,B+ 樹適合范圍查詢拳喻;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末哭当,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子冗澈,更是在濱河造成了極大的恐慌钦勘,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亚亲,死亡現(xiàn)場(chǎng)離奇詭異彻采,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)捌归,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門肛响,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人惜索,你說我怎么就攤上這事特笋。” “怎么了巾兆?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵猎物,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我臼寄,道長(zhǎng)霸奕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任吉拳,我火速辦了婚禮质帅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘留攒。我一直安慰自己煤惩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布炼邀。 她就那樣靜靜地躺著魄揉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拭宁。 梳的紋絲不亂的頭發(fā)上洛退,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音杰标,去河邊找鬼兵怯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛腔剂,可吹牛的內(nèi)容都是我干的媒区。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼袜漩!你這毒婦竟也來了绪爸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤宙攻,失蹤者是張志新(化名)和其女友劉穎奠货,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體粘优,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仇味,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了雹顺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丹墨。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嬉愧,靈堂內(nèi)的尸體忽然破棺而出贩挣,到底是詐尸還是另有隱情,我是刑警寧澤没酣,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布王财,位于F島的核電站,受9級(jí)特大地震影響裕便,放射性物質(zhì)發(fā)生泄漏绒净。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一偿衰、第九天 我趴在偏房一處隱蔽的房頂上張望挂疆。 院中可真熱鬧,春花似錦下翎、人聲如沸缤言。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胆萧。三九已至,卻和暖如春俐东,著一層夾襖步出監(jiān)牢的瞬間跌穗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工虏辫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瞻离,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓乒裆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鹤耍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355