瘋狂創客圈 經典圖書 : 《Netty Zookeeper Redis 高併發實戰》 面試必備 + 面試必備 + 面試必備 【博客園總入口 】html
瘋狂創客圈 經典圖書 : 《SpringCloud、Nginx高併發核心編程》 大廠必備 + 大廠必備 + 大廠必備 【博客園總入口 】面試
入大廠+漲工資必備: 高併發【 億級流量IM實戰】 實戰系列 【 SpringCloud Nginx秒殺】 實戰系列 【博客園總入口 】redis
跳錶,是基於鏈表實現的一種相似「二分」的算法。它能夠快速的實現增,刪,改,查操做。算法
咱們先來看一下單向鏈表如何實現查找:編程
鏈表,相信你們都不陌生,維護一個有序的鏈表是一件很是簡單的事情,咱們都知道,在一個有序的鏈表裏面,查找某個數據的時候須要的時間複雜度爲O(n).數據結構
怎麼提升查詢效率呢?若是咱們給該單鏈表加一級索引,將會改善查詢效率。併發
如圖所示,當咱們每隔一個節點就提取出來一個元素到上一層,把這一層稱做索引,上層的索引節點都加上一個down指針指向原始節點。高併發
當咱們查找元素11的時候,單鏈表須要比較6次,而加過索引的兩級鏈表只須要比較4次。當數據量增大到必定程度的時候,效率將會有顯著的提高。工具
若是咱們再加多幾級索引的話,效率將會進一步提高。這種鏈表加多級索引的結構,就叫作跳錶。
3d
跳錶的查詢時間複雜度能夠達到O(logn)
跳錶就是這樣的一種數據結構,結點是跳過一部分的,從而加快了查詢的速度。跳錶跟紅黑樹又有什麼差異呢?既然二者的算法複雜度差很少,爲何Redis的有序集合SortedSet要使用跳錶實現,而不使用紅黑樹呢?
Redis 中的有序集合是經過跳錶來實現的,嚴格點講,其實還用到了散列表。
若是你去查看 Redis 的開發手冊,就會發現,Redis 中的有序集合支持的核心操做主要有下面這幾個:
插入一個數據;
刪除一個數據;
查找一個數據;
按照區間查找數據(好比查找值在 [100, 356] 之間的數據);
迭代輸出有序序列。
其中,插入、刪除、查找以及迭代輸出有序序列這幾個操做,紅黑樹也能夠完成,時間複雜度跟跳錶是同樣的。可是,按照區間來查找數據這個操做,紅黑樹的效率沒有跳錶高。對於按照區間查找數據這個操做,跳錶能夠作到 O(logn) 的時間複雜度定位區間的起點,而後在原始鏈表中順序日後遍歷就能夠了。這樣作很是高效。
固然,Redis之因此用跳錶來實現有序集合,還有其餘緣由,好比,跳錶更容易代碼實現。雖然跳錶的實現也不簡單,但比起紅黑樹來講仍是好懂、好寫多了,而簡單就意味着可讀性好,不容易出錯。還有,跳錶更加靈活,它能夠經過改變索引構建策略,有效平衡執行效率和內存消耗
假如咱們要查詢11,那麼咱們從最上層出發,發現下一個是5,再下一個是13,已經大於11,因此進入下一層,下一層的一個是9,查找下一個,下一個又是13,再次進入下一層。最終找到11。
是否是很是的簡單?咱們能夠把查找的過程總結爲一條二元表達式(下一個是否大於結果?下一個:下一層)。理解跳錶的查詢過程很是重要,試試看查詢其餘數字,只要你理解了查詢,後面兩種都很是簡單。
接下來看插入,咱們先看理想的跳躍表結構,L2層的元素個數是L1層元素個數的1/2,L3層的元素個數是L2層的元素個數的1/2,以此類推。從這裏,咱們能夠想到,只要在插入時儘可能保證上一層的元素個數是下一層元素的1/2,咱們的跳躍表就能成爲理想的跳躍表。那麼怎麼樣才能在插入時保證上一層元素個數是下一層元素個數的1/2呢?很簡單,拋硬幣就能解決了!
假設元素X要插入跳躍表,很顯然,L1層確定要插入X。那麼L2層要不要插入X呢?咱們但願上層元素個數是下層元素個數的1/2,因此咱們有1/2的機率但願X插入L2層,那麼拋一下硬幣吧,正面就插入,反面就不插入。那麼L3到底要不要插入X呢?相對於L2層,咱們仍是但願1/2的機率插入,那麼繼續拋硬幣吧!以此類推,元素X插入第n層的機率是(1/2)的n次。這樣,咱們能在跳躍表中插入一個元素了。
跳躍表的初試狀態以下圖,表中沒有一個元素:
若是咱們要插入元素2,首先是在底部插入元素2,以下圖:
而後咱們拋硬幣,結果是正面,那麼咱們要將2插入到L2層,以下圖
繼續拋硬幣,結果是反面,那麼元素2的插入操做就中止了,插入後的表結構就是上圖所示。接下來,咱們插入一個新元素33,跟元素2的插入同樣,如今L1層插入33,以下圖:
而後拋硬幣,結果是反面,那麼元素33的插入操做就結束了,插入後的表結構就是上圖所示。接下來,咱們插入一個新元素55,首先在L1插入55,插入後以下圖:
而後拋硬幣,結果是正面,那麼L2層須要插入55,以下圖:
繼續拋硬幣,結果又是正面,那麼L3層須要插入55,以下圖:
繼續拋硬幣,結果又是正面,那麼要在L4插入55,結果以下圖:
繼續拋硬幣,結果是反面,那麼55的插入結束,表結構就如上圖所示。
固然,不可能無限的進行層數增加。除了根據一種隨機算法獲得的層數以外,爲了避免讓層數過大,還會有一個最大層數MAX_LEVEL限制,隨機算法生成的層數不得大於該值。
以此類推,咱們插入剩餘的元素。固然由於規模小,結果極可能不是一個理想的跳躍表。可是若是元素個數n的規模很大,學過幾率論的同窗都知道,最終的表結構確定很是接近於理想跳躍表。
固然,這樣的分析在感性上是很直接的,可是時間複雜度的證實實在複雜,在此我就不深究了,感興趣的能夠去看關於跳躍表的paper。
再討論刪除,刪除操做沒什麼講的,直接刪除元素,而後調整一下刪除元素後的指針便可。跟普通的鏈表刪除操做徹底同樣。
插入和刪除的時間複雜度就是查詢元素插入位置的時間複雜度,這不難理解,因此是O(logn)。
瘋狂創客圈 - Java高併發研習社羣,爲你們開啓大廠之門