手寫一個跳錶,跪了。。。

文章很長,且持續更新,建議收藏起來,慢慢讀!瘋狂創客圈總目錄 博客園版 爲您奉上珍貴的學習資源 :

免費贈送 :《尼恩Java面試寶典》 持續更新+ 史上最全 + 面試必備 2000頁+ 面試必備 + 大廠必備 +漲薪必備
免費贈送 :《尼恩技術聖經+高併發系列PDF》 ,幫你 實現技術自由,完成職業升級, 薪酬猛漲!加尼恩免費領
免費贈送 經典圖書:《Java高併發核心編程(卷1)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 經典圖書:《Java高併發核心編程(卷2)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 經典圖書:《Java高併發核心編程(卷3)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領

免費贈送 資源寶庫: Java 必備 百度網盤資源大合集 價值>10000元 加尼恩領取


手寫一個跳錶,跪了。。。

尼恩說在前面

在40歲老架構師 尼恩的讀者交流羣(50+)中,最近有小夥伴拿到了一線互聯網企業如得物、阿里、滴滴、極兔、有贊、希音、百度、網易、美團、螞蟻、得物的面試資格,遇到很多很重要的相關面試題:

手寫一個跳錶?

redis爲什麼用跳錶不用B+樹嗎?

最近有小夥伴在螞蟻、面試字節,都問到了相關的面試題,可以說是逢面必問。

小夥伴沒有系統的去梳理和總結,所以支支吾吾的說了幾句,面試官不滿意,面試掛了。

所以,尼恩給大家做一下系統化、體系化的梳理,使得大家內力猛增,可以充分展示一下大家雄厚的 “技術肌肉”,讓面試官愛到 “不能自已、口水直流”,然後實現”offer直提”。

當然,這道面試題,以及參考答案,也會收入咱們的 《尼恩Java面試寶典PDF》V175版本,供後面的小夥伴參考,提升大家的 3高 架構、設計、開發水平。

《尼恩 架構筆記》《尼恩高併發三部曲》《尼恩Java面試寶典》的PDF,請到文末公號【技術自由圈】獲取

本文作者:

  • 第一作者 Moen (負責寫初稿 )
  • 第二作者 尼恩 (40歲老架構師, 負責提升此文的 技術高度,讓大家有一種 俯視 技術的感覺)

本文目錄

- 尼恩說在前面

- 什麼是跳錶

- 對有序鏈表查詢優化過程

- 跳錶的特點

- 隨機層數在性能上的優化

- Redis中ZSet是怎麼實現的

- Redis中ZSet底層數據結構

- zpilist和skiplist之間何時進行轉換

- Redis對跳錶的實現及改進與優化

- 跳錶與平衡樹、哈希表的比較

- Redis爲什麼使用跳錶而不用平衡樹

- 跳錶與B+樹的比較

- 基於Java實現跳錶

- 總結

- 說在最後:有問題找老架構取經

什麼是跳躍表(skiplist)?

Redis 是一個開源(BSD許可)的,內存中的數據結構存儲系統,它可以用作數據庫、緩存和消息中間件。

Redis使用跳躍表作爲有序集合鍵的底層實現之一,如果一個有序集合包含的元素數量比較多,又或者有序集合中元素的成員(member)是比較長的字符串時,Redis就會使用跳躍表來作爲有序集合鍵的底層實現。

跳躍表(skiplist)是一種隨機化的數據, 由 William Pugh 在論文《Skip lists: a probabilistic alternative to balanced trees》中提出, 跳躍表以有序的方式在層次化的鏈表中保存元素, 效率和平衡樹媲美 —— 查找、刪除、添加等操作都可以在對數期望時間下完成, 並且比起平衡樹來說, 跳躍表的實現要簡單直觀得多。

跳錶是一種帶多級索引的鏈表,本質上也是一種查找結構, 用於解決算法中的查找問題,即根據給定的key,快速查找它所在的位置(value)。跳躍表(skiplist)是一種有序數據結構,它通過在每個節點中維持多個指向其他節點的指針,從而達到快速訪問節點的目的。

跳躍表支持平均0 (1ogN)、最壞O(N) 複雜度的節點查找,還可以通過順序性操作來批量處理節點。

在大部分情況下,跳躍表的效率可以和平衡樹相媲美,並且因爲跳躍表的實現比平衡樹要來得更爲簡單,所以有不少程序都使用跳躍表來代替平衡樹。

從直觀理解的維度來繪製,一個3層索引結構的skiplist , demo 示意圖如下:

此圖來自於 尼恩和小夥伴們的 本系列文章的第一篇:

字節面試: Mysql爲什麼用B+樹,不用跳錶?

換一種方式,從編碼實現的維度來繪製, 一個3層索引結構的skiplist 的 demo 示意圖如下:

對有序鏈表的查詢優化過程

對於一個單鏈表來講,即便鏈表中存儲的數據是有序的,如果我們要想在其中查找某個數據,也只能從頭到尾遍歷鏈表。

這樣查找效率就會很低,時間複雜度會很高,是 O(n)。

step1:建立一級索引

對鏈表中每兩個節點建立第一級索引 , 大致的方法如下:

假如爲每相鄰兩個節點增加一個指針,讓指針指向下下個節點 ,如下圖:

這樣所有新增加的指針連成了一個新的鏈表(上圖中第一級索引指向的鏈表),

但第一級索引 包含的節點個數只有原來的一半(上圖中是5, 9, 16)。

請注意:實際應用中的skiplist每個節點應該包含key和value兩部分。

這裏爲了方便描述,並沒有具體區分key和value,但實際上列表中是按照key進行排序的,查找過程也是根據key進行比較。

現在當我們想查找數據的時候,查找的過程如下:

  • 可以先第一級索引層遍歷進行查找。

  • 當碰到比待查數據大的節點時,再回到原來的鏈表中進行查找。

比如,我們想查找14,查找的路徑是沿着下圖中標紅的指針所指向的方向進行的:

  • 14首先和5比較,再和9比較,比它們都大,繼續向後比較。
  • 但14和16比較的時候,比16要小,因此回到下面的鏈表(原鏈表),與13比較。
  • 14比13要大,沿下面的指針繼續向後和16比較。14比16小,說明待查數據14在原鏈表中不存在,而且它的插入位置應該在13和16之間。

在這個查找過程中,由於新增加的指針,我們不再需要與鏈表中每個節點逐個進行比較了。

通過第一層索引,我們發現:需要比較的節點數大概只有原來的一半

step2:建立二級索引

利用同樣的方式,我們可以在第一層新產生的鏈表上,繼續爲每相鄰的兩個節點增加一個指針,從而產生第二層鏈表,這一層鏈表是第二層索引。

第二層索引如下圖:

在這個新的第二層索引結構上,如果我們還是查找14,

那麼沿着最上層鏈表首先要比較的是9,發現14比9大,接下來我們就知道只需要到9的後面去繼續查找,從而一下子跳過了9前面的所有節點。

從上述過程我們看出,加了一層索引之後,查找一個結點需要遍歷的結點個數減少了,也就是說查找效率提高了。

跳錶的特點

跳錶是一種動態數據結構,支持快速地插入、刪除、查找操作,時間複雜度都是 O(logn)。

跳錶的空間複雜度是 O(n)。不過,跳錶的實現非常靈活,可以通過改變索引構建策略,有效平衡執行效率和內存消耗。

跳錶即表示跳躍表,指的就是除了最下面第1層鏈表之外,它會產生若干層稀疏的鏈表,這些鏈表裏面的指針故意跳過了一些節點(而且越高層的鏈表跳過的節點越多)。這就使得我們在查找數據的時候,可由高層鏈表到底層鏈表逐層降低,在這個過程中,跳過了一些節點,從而也就加快了查找速度。

跳錶使用空間換時間的設計思路,通過構建多級索引來提高查詢的效率,實現了基於鏈表的“二分查找”。即上面每一層鏈表的節點個數,是下面一層的節點個數的一半。

隨機層數在插入性能上的優化

新插入一個節點之後,就會打亂上下相鄰兩層鏈表上節點個數嚴格的2:1的對應關係。

如果要維持這種對應關係,就必須把新插入的節點後面的所有節點(也包括新插入的節點)重新進行調整,這會讓時間複雜度重新蛻化成O(n)。刪除數據也有同樣的問題。

skiplist爲了避免這一問題,它不要求上下相鄰兩層鏈表之間的節點個數有嚴格的對應關係,而是爲每個節點隨機出一個層數(level)。

比如,一個節點隨機出的層數是3,那麼就把它鏈入到第1層到第3層這三層鏈表中。

爲了表達清楚,下面通過多個圖的方式,一步一步展示了插入操作如何通過隨機層數,決定一個節點要插入跳錶的哪幾層的問題:

上圖, 插入9的時候, 隨機層數爲2,就需要插入2層:

  • 在原始鏈表層,插入 9。
  • 和第一個索引層,插入 9。

上圖, 插入5的時候, 隨機層數爲4,就需要插入4層:

  • 在原始鏈表層,插入 5。
  • 和第一個索引層,插入 5。
  • 和第二個索引層,插入 5。
  • 和第三個索引層,插入 5。

上圖, 插入1的時候, 隨機層數爲1,就需要插入1層:

  • 在原始鏈表層,插入 1。

上圖, 插入21的時候, 隨機層數爲3,就需要插入3層:

  • 在原始鏈表層,插入 21。
  • 和第一個索引層,插入 21。
  • 和第二個索引層,插入 21。

上圖, 插入6的時候, 隨機層數爲1,就需要插入1層:

  • 在原始鏈表層,插入 1。

上圖, 插入16的時候, 隨機層數爲1,就需要插入1層:

  • 在原始鏈表層,插入 1。

從上面skiplist的創建和插入過程可以看出,每一個節點的層數(level)是隨機出來的,而且新插入一個節點不會影響其它節點的層數。

skiplist 在插入的效率比較高,插入操作只需要修改插入節點前後的指針,而不需要對很多節點都進行調整。這就降低了插入操作的複雜度。

與之相比,B+樹在插入的時候要維護樹的平衡,插入過程中會發生 page 的分裂, 插入的性能就會差很多,具體請見:

字節面試: Mysql爲什麼用B+樹,不用跳錶?

插入的性能很高,這是skiplist的一個很重要的特性,這讓它在插入性能上明顯優於平衡樹的方案。

skiplist執行插入,需要計算隨機數,是一個很關鍵的過程,它對skiplist的統計特性有着很重要的影響。

這並不是一個普通的服從均勻分佈的隨機數,它的計算過程如下:

  • 首先,每個節點肯定都有第1層指針(每個節點都在第1層鏈表裏)。
  • 如果一個節點有第i層(i>=1)指針(即節點已經在第1層到第i層鏈表中),那麼它有第(i+1)層指針的概率爲p。
  • 節點最大的層數不允許超過一個最大值,記爲MaxLevel。

跳錶查找元素的過程

剛剛創建的這個skiplist總共包含4層鏈表,現在假設我們在它裏面依然查找14,下圖給出了查找路徑:

Redis中ZSet是怎麼實現的

Redis中 ZSet(Sorted Set)是Redis中的一種特殊數據結構,它內部維護一個有序的dict,這個字典dict包括兩個屬性:成員(member)、分數(score,double類型)。

這個ZSet 結構價值很大:

  • 可以幫助我們實現排行榜、
  • 朋友圈點贊等記分類型的排行數據,
  • 以及實現延遲隊列、限流。

Redis中ZSet實現包括多種結構,有ziplist(壓縮列表)、skiplist(跳錶)、listpack(緊湊列表,在Redis5.0中新增)。

listpack是爲了替代ziplist,在Redis7.0中已經徹底棄用ziplist。

Redis中Zset底層數據結構

前面提到過,Redis中的ZSet是在dict、ziplist(listpack)、skiplist基礎上構建起來的:

  • 當數據較少時,sorted set是由一個ziplist來實現的。
  • 當數據多的時候,sorted set是由一個叫zset的數據結構來實現的,這個zset包含一個dict + 一個skiplist。

當數據多的時候,zset包含一個dict + 一個skiplist:

  • dict用來查詢數據到分數(score)的對應關係,

  • 而skiplist用來根據分數查詢數據(可能是範圍查找)。

ziplist就是由很多數據項組成的一大塊連續內存。由於sorted set的每一項元素都由數據和score組成,因此,當使用zadd命令插入一個(數據, score)對的時候,底層在相應的ziplist上就插入兩個數據項:數據在前,score在後。

ziplist的主要優點是節省內存,但它上面的查找操作只能按順序查找(可以正序也可以倒序)。因此,sorted set的各個查詢操作,就是在ziplist上從前向後(或從後向前)一步步查找,每一步前進兩個數據項,跨域一個(數據, score)對。

ZSet中的字典和跳錶佈局

其中skiplist用來實現有序集合,其中每個元素按照其分值大小在跳錶中進行排序,跳錶的插入、刪除和查找操作時間複雜度都是O(1),能夠保證較好的性能。

dict用來實現元素到分值的映射,其中元素作爲鍵,分值作爲值。哈希表的插入、刪除和查找操作的時間複雜度都是O(1),具備非常高的性能。

redis的zpilist和skiplist之間何時進行轉換

隨着數據的插入,Redis底層會將ziplist轉換成skiplist,那麼到底插入多少數據纔會轉換,下面進行分析。

本文主要涉及兩個Redis配置(在redis.conf中的ADVANCED CONFIG部分)

Similarly to hashes and lists, sorted sets are also specially encoded in
order to save a lot of space. This encoding is only used when the length and
elements of a sorted set are below the following limits:

Redis7.0之前的配置

zset-max-ziplist-entries 128 // ziplist中元素個數
zset-max-ziplist-value 64 // ziplist中元素大小

Redis7.0之後的配置

zset-max-listpack-entries 128 // listpack中元素個數
zset-max-listpack-value 64 // listpack中元素大小

在Redis中,以上兩個配置說明,只有當長度和排序集的元素個數,同時滿足以下兩個條件時會使用ziplist(listpack)作爲其內部表示,具體條件如下:

  • 元素數量少:集合中的元素數量必須小於配置的閾值:zset-max-ziplist-entries(zset-max-listpack-entries)

  • 插入數據長度:當ZSet中插入的任意一個數據的長度超過64的時候。

總結:當元素數量少於128,每個元素的長度都小於64字節的時候,Redis使用ziplist(listpack),否則使用是skiplist。

/* Convert the sorted set object into a listpack if it is not already a listpack
 * and if the number of elements and the maximum element size and total elements size
 * are within the expected ranges. */
void zsetConvertToListpackIfNeeded(robj *zobj, size_t maxelelen, size_t totelelen) {
    if (zobj->encoding == OBJ_ENCODING_LISTPACK) return;
    zset *zset = zobj->ptr;

    if (zset->zsl->length <= server.zset_max_listpack_entries &&
        maxelelen <= server.zset_max_listpack_value &&
        lpSafeToAdd(NULL, totelelen))
    {
        zsetConvert(zobj,OBJ_ENCODING_LISTPACK);
    }
}

Redis對跳錶的實現及改進與優化

Redis 的跳躍表是由 redis.h/zskiplistNode和 redis.h/zskiplist 兩個結構定義,其中 zskiplistNode 用於表示跳躍節點,而 zskiplist 結構則用於保存跳躍表節點的相關信息,比如節點的數量以及指向表頭節點和表尾節點的指針等等。

上圖最左邊的是 zskiplist 結構,該結構包含以下屬性:

  • header:指向跳躍表的表頭節點

  • tail:指向跳躍表的表尾節點

  • level:記錄目前跳躍表內,層數最大的那個節點層數(表頭節點的層數不計算在內)

  • length:記錄跳躍表的長度,也就是跳躍表目前包含節點的數量(表頭節點不計算在內)

位於 zskiplist 結構右側是四個 zskiplistNode 結構,該結構包含以下屬性:

  • 層(level):節點中用 L1、L2、L3 等字樣標記節點的各個層,L1 代表第一層,L2 代表第二層,以此類推。每個層都帶有兩個屬性:前進指針和跨度。前進指針用於訪問位於表尾方向的其它節點,而跨度則記錄了前進指針所指向節點和當前節點的距離。

  • 後退(backward)指針:節點中用 BW 字樣標識節點的後退指針,它指向位於當前節點的前一個節點。後退指針在程序從表尾向表頭遍歷時使用。

  • 分值(score):各個節點中的 1.0、2.0 和 3.0 是節點所保存的分值。在跳躍表中,節點按各自所保存的分值從小到大排列。

總結起來,Redis中的skiplist跟前面介紹的經典的skiplist相比,有如下不同:

  • 分數(score)允許重複,即skiplist的key允許重複。這在最開始介紹的經典skiplist中是不允許的。
  • 在比較時,不僅比較分數(相當於skiplist的key),還比較數據本身。在Redis的skiplist實現中,數據本身的內容唯一標識這份數據,而不是由key來唯一標識。另外,當多個元素分數相同的時候,還需要根據數據內容來進字典排序。
  • 第1層鏈表不是一個單向鏈表,而是一個雙向鏈表。這是爲了方便以倒序方式獲取一個範圍內的元素。
  • 在skiplist中可以很方便地計算出每個元素的排名(rank)。

跳錶與平衡樹、哈希表的比較

  • skiplist和各種平衡樹(如AVL、紅黑樹等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做單個key的查找,不適宜做範圍查找。所謂範圍查找,指的是查找那些大小在指定的兩個值之間的所有節點。
  • 在做範圍查找的時候,平衡樹比skiplist操作要複雜。在平衡樹上,我們找到指定範圍的小值之後,還需要以中序遍歷的順序繼續尋找其它不超過大值的節點。如果不對平衡樹進行一定的改造,這裏的中序遍歷並不容易實現。而在skiplist上進行範圍查找就非常簡單,只需要在找到小值之後,對第1層鏈表進行若干步的遍歷就可以實現。
  • 平衡樹的插入和刪除操作可能引發子樹的調整,邏輯複雜,而skiplist的插入和刪除只需要修改相鄰節點的指針,操作簡單又快速。
  • 從內存佔用上來說,skiplist比平衡樹更靈活一些。一般來說,平衡樹每個節點包含2個指針(分別指向左右子樹),而skiplist每個節點包含的指針數目平均爲1/(1-p),具體取決於參數p的大小。如果像Redis裏的實現一樣,取p=1/4,那麼平均每個節點包含1.33個指針,比平衡樹更有優勢。
  • 查找單個key,skiplist和平衡樹的時間複雜度都爲O(log n),大體相當;而哈希表在保持較低的哈希值衝突概率的前提下,查找時間複雜度接近O(1),性能更高一些。所以我們平常使用的各種Map或dictionary結構,大都是基於哈希表實現的。
  • 從算法實現難度上來比較,skiplist比平衡樹要簡單得多。

Redis爲什麼使用跳錶而不使用平衡樹

關於這個問題,Redis作者是這麼說的:

There are a few reasons:
1、They are not very memory intensive. It's up to you basically. Changing parameters about the probability of a node to have a given number of levels will make then less memory intensive than btrees.
2、A sorted set is often target of many ZRANGE or ZREVRANGE operations, that is, traversing the skip list as a linked list. With this operation the cache locality of skip lists is at least as good as with other kind of balanced trees.
3、They are simpler to implement, debug, and so forth. For instance thanks to the skip list simplicity I received a patch(already in Redis master) with augmented skip lists implementing ZRANK in O(log(N)). It required little changes to the code.

主要是從內存佔用、對範圍查找的支持、實現難易程度這三方面總結的原因,簡單翻譯如下:

  • 它們不是非常內存密集型的。基本上由你決定。改變關於節點具有給定級別數的概率的參數將使其比 btree 佔用更少的內存。

  • Zset 經常需要執行 ZRANGE 或 ZREVRANGE 的命令,即作爲鏈表遍歷跳錶。通過此操作,跳錶的緩存局部性至少與其他類型的平衡樹一樣好。

  • 它們更易於實現、調試等。例如,由於跳錶的簡單性,我收到了一個補丁(已經在Redis master中),其中擴展了跳錶,在 O(log(N) 中實現了 ZRANK。它只需要對代碼進行少量修改。

關於上述觀點,做幾點補充如下:

  • 從內存佔用上來比較,跳錶比平衡樹更靈活一些。平衡樹每個節點包含 2 個指針(分別指向左右子樹),而跳錶每個節點包含的指針數目平均爲 1/(1-p),具體取決於參數 p 的大小。如果像 Redis裏的實現一樣,取 p=1/4,那麼平均每個節點包含 1.33 個指針,比平衡樹更有優勢。

  • 在做範圍查找的時候,跳錶比平衡樹操作要簡單。在平衡樹上,我們找到指定範圍的小值之後,還需要以中序遍歷的順序繼續尋找其它不超過大值的節點。如果不對平衡樹進行一定的改造,這裏的中序遍歷並不容易實現。而在跳錶上進行範圍查找就非常簡單,只需要在找到小值之後,對第 1 層鏈表進行若干步的遍歷就可以實現。

  • 從算法實現難度上來比較,跳錶比平衡樹要簡單得多。平衡樹的插入和刪除操作可能引發子樹的調整,邏輯複雜,而跳錶的插入和刪除只需要修改相鄰節點的指針,操作簡單又快速。

跳錶與B+樹的比較

  • 相同點:skiplist和B+樹的最下面一層,都包含了所有數據,且都是有序的,適合用於範圍查詢。

  • 不同點:

    • B+樹本質上是一種多叉平衡二叉樹。當數據庫表不斷插入新的數據時,爲了維持B+樹的平衡,B+樹會不斷分裂調整數據頁,來保證B+樹的子樹們的高度層級儘量一致(一般最多差一個層級)。適合讀多寫少的場景。(存儲引擎RocksDB內部使用了跳錶,對比使用B+樹的innodb,雖然寫性能更好,但讀性能屬實差了些。)

    • skiplist在新增/刪除數據時,依靠隨機函數,即可確定是否需要向上添加索引,達到一個二分的效果,無需平衡數據結構,少了旋轉平衡的開銷。

    • skiplist佔用更少的內存,且更容易實現、調試。

  • B+樹是多叉平衡搜索樹,只需要3層左右就能存放2kw左右的數據,同樣情況下跳錶則需要24層左右,假設層高對應磁盤IO,那麼B+樹的讀性能會比跳錶要好,因此mysql選了B+樹做索引。

  • redis的讀寫全在內存裏進行操作,不涉及磁盤IO,同時跳錶實現簡單,相比B+樹、AVL樹、少了旋轉樹結構的開銷,因此redis使用跳錶來實現ZSET,而不是樹結構。

總之:

  • B+樹是 "磁盤友好" 型的 數據機構,適合於 DB。

  • 跳錶 是 "內存友好" 型的數據結構, 適合於 Cache。

字節的真題:手寫一個跳錶

第一步:定義好 跳錶的節點

每一個節點, 都帶着一個 指針數組,

一個最大16層的跳錶,每一個node結構 包含一個 規模爲16大小的指針數組

第二步:定義好 生產 隨機層數的方法



  // 理論來講,一級索引中元素個數應該佔原始數據的 50%,二級索引中元素個數佔 25%,三級索引12.5% ,一直到最頂層。
  // 因爲這裏每一層的晉升概率是 50%。對於每一個新插入的節點,都需要調用 randomLevel 生成一個合理的層數。
  // 該 randomLevel 方法會隨機生成 1~MAX_LEVEL 之間的數,且 :
  //        50%的概率返回 1
  //        25%的概率返回 2
  //      12.5%的概率返回 3 ...
  private int randomLevel() {
    int level = 1;

    while (Math.random() < SKIPLIST_P && level < MAX_LEVEL)
      level += 1;
    return level;
  }

第三步:定義好 數據插入的方法

第四步:定義好 數據刪除的方法

寫到這裏,字節的offer到手

40歲老架構師尼恩提示大家, 搞定 手寫跳錶 也很容易的哦。

鎖定5000頁《尼恩Java面試寶典》 ,大廠機會,滾滾而來。

總結

Redis 中的有序集合支持的核心操作主要有下面這幾個:

  • 插入一個數據;

  • 刪除一個數據;

  • 按照區間查找數據(比如查找值在[100, 356]之間的數據);

  • 迭代輸出有序序列。

其中,插入、刪除、查找以及迭代輸出有序序列這幾個操作,紅黑樹也可以完成,時間複雜度跟跳錶是一樣的。但是,按照區間來查找數據這個操作,紅黑樹的效率沒有跳錶高。

對於按照區間查找數據這個操作,跳錶可以做到 O(logn) 的時間複雜度定位區間的起點,然後在原始鏈表中順序往後遍歷就可以了。這樣做非常高效。

當然,Redis 之所以用跳錶來實現有序集合,還有其他原因,比如,跳錶更容易代碼實現。雖然跳錶的實現也不簡單,但比起紅黑樹來說還是好懂、好寫多了,而簡單就意味着可讀性好,不容易出錯。還有,跳錶更加靈活,它可以通過改變索引構建策略,有效平衡執行效率和內存消耗。

不過,跳錶也不能完全替代紅黑樹。因爲紅黑樹比跳錶的出現要早一些,很多編程語言中的 Map 類型都是通過紅黑樹來實現的。我們做業務開發的時候,直接拿來用就可以了,不用費勁自己去實現一個紅黑樹,但是跳錶並沒有一個現成的實現,所以在開發中,如果你想使用跳錶,必須要自己實現。

說在最後:有問題找老架構取經

跳錶 、B+ 相關的面試題,是非常重要的面試題。

高級崗位、大廠崗位,這個必備。

此文,是 尼恩團隊寫的 跳錶 、B+ 本系列文章的第二篇,兩篇文章結合使用效果更佳, 第一篇如下:

字節面試: Mysql爲什麼用B+樹,不用跳錶?

手寫跳錶? redis爲何要用跳錶?

如果能按照以上的內容,對答如流,如數家珍,基本上 面試官會被你 震驚到、吸引到。

最終,讓面試官愛到 “不能自已、口水直流”。offer, 也就來了。

在面試之前,建議大家系統化的刷一波 5000頁《尼恩Java面試寶典》V174,在刷題過程中,如果有啥問題,大家可以來 找 40歲老架構師尼恩交流。

另外,如果沒有面試機會,可以找尼恩來幫扶、領路。

尼恩已經指導了大量的就業困難的小夥伴上岸,前段時間,幫助一個40歲+就業困難小夥伴拿到了一個年薪100W的offer,小夥伴實現了 逆天改命 。

技術自由的實現路徑:

實現你的 架構自由:

喫透8圖1模板,人人可以做架構

10Wqps評論中臺,如何架構?B站是這麼做的!!!

阿里二面:千萬級、億級數據,如何性能優化? 教科書級 答案來了

峯值21WQps、億級DAU,小遊戲《羊了個羊》是怎麼架構的?

100億級訂單怎麼調度,來一個大廠的極品方案

2個大廠 100億級 超大流量 紅包 架構方案

… 更多架構文章,正在添加中

實現你的 響應式 自由:

響應式聖經:10W字,實現Spring響應式編程自由

這是老版本 《Flux、Mono、Reactor 實戰(史上最全)

實現你的 spring cloud 自由:

Spring cloud Alibaba 學習聖經》 PDF

分庫分表 Sharding-JDBC 底層原理、核心實戰(史上最全)

一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之間混亂關係(史上最全)

實現你的 linux 自由:

Linux命令大全:2W多字,一次實現Linux自由

實現你的 網絡 自由:

TCP協議詳解 (史上最全)

網絡三張表:ARP表, MAC表, 路由表,實現你的網絡自由!!

實現你的 分佈式鎖 自由:

Redis分佈式鎖(圖解 - 秒懂 - 史上最全)

Zookeeper 分佈式鎖 - 圖解 - 秒懂

實現你的 王者組件 自由:

隊列之王: Disruptor 原理、架構、源碼 一文穿透

緩存之王:Caffeine 源碼、架構、原理(史上最全,10W字 超級長文)

緩存之王:Caffeine 的使用(史上最全)

Java Agent 探針、字節碼增強 ByteBuddy(史上最全)

實現你的 面試題 自由:

4800頁《尼恩Java面試寶典 》 40個專題

免費獲取11個技術聖經PDF:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章