各位看官大大們,週末好!html
做爲一個Java後端開發,要想得到比較可觀的工資,Redis基本上是必會的(不要問我爲何知道,問就是被問過無數次)。
那麼Redis是什麼,它到底擁有什麼神祕的力量,能得到衆多公司的青睞?接下來就由小編我帶你們來揭祕Redis的五種基本數據結構。java
Redis是C語音編寫的基於內存的數據結構存儲系統。它能夠看成數據庫、緩存和消息中間件。 它支持多種類型的數據結構,如 字符串(strings),
列表(lists), 字典(dictht),集合(sets), 有序集合(sorted sets)。一般咱們在項目中能夠用它來作緩存、記錄簽到數據、分佈式鎖等等。
要使用Redis首先咱們來了解一下它的五種基礎數據結構。node
1.字符串(strings)
Redis擁有兩種字符串表述方式,其一是C語言傳統的字符串表述方式,經常使用Redis代碼中字符串常量等一些無需對字符串進行修改的地方。面試
第二種是本身封裝了一種名爲簡單動態字符串(simple dynamic string)簡稱SDS的抽象類型,這個纔是咱們存儲字符串時所使用的對象,等同於java中的StringBuilder。redis
SDS的結構以下:算法
struct sdshdr{ //記錄字符數組中已經使用的字節數量 便是字符串的長度 int len; //記錄字符數組中未使用的字節數 int free; //字符數組 用於保存字符串 char buf[]; }
結構以下圖所示:數據庫
至於Redis爲何要使用這樣的結構,其實和java使用StringBuilder的思惟是截然不同。爲了方便修改和提高性能。好比C的字符串獲取字符串長度時要遍歷整個字符數組。
其時間複雜度是O(n),而SDS則能夠直接獲取len,時間複雜度爲O(1)。修改字符串N次字符串而且字符串和之前的長度不一致時,C普通字符串長度必然須要執行N次內存重分配。
而SDS存在預擴容,因此最多須要執行N次內存分配。
注:與擴容其本質和list相似,在須要的長度大於如今數組的長度時,會觸發字符串擴容,當數據小於1M時,字符數組每次擴容都是其原來容量的2倍。1M後每次擴容新增1M容量。後端
2.列表
Redis中的列表至關於java中的LinkedList,它是一個雙向鏈表,插入和刪除都擁有極好的性能,時間複雜度爲O(1),可是隨機查找比較慢,時間複雜度爲O(n)。雖然能夠將列表
當成一個LinkedList,可是在Redis內部列表並非一個簡單的雙向鏈表的實現。在列表保存元素個數小於512個且每一個元素長度小於64字節的時候爲了節省內存其底層實現是一塊
連續內存來存儲,稱之爲ziplist壓縮列表。當不知足以前的兩個條件時則改用quicklist快速列表來存儲原元素。數組
ziplist壓縮列表:
壓縮列表是Redis爲了節約內存而開發的,是由一系列特殊編碼的連續內存塊組成的順序型數據結構。一個壓縮列表能夠包含任意多個節點,每一個節點保存一個字節數組或者一個整數值。緩存
struct ziplist<T>{ int32 zlbytes; int32 zltail_offset; int16 zllemhth; T[] entries; int8 zlend; }
其結構以下圖所示:
節點結構以下:
struct entry{ int<var> previous_entry_length;//前一個原數的字節長度 int<var> encoding;//元數類型編碼 optional byte[] content;//元素內容 }
這裏有一個點要注意,若是entryX+1和起身後的節點的長度都都在250~253個字節之間的話,若是entryX長度變成了254個字節。那麼entryX+1中的previous_entry_length將擴容成5個字節,
這將致使entryX+1的總體長度也會大於254個字節,引發entryX+2個字節中的previous_entry_length也發生擴容,使得entryX+2的總體長度也超過254。並對後面的節點形成連鎖影響這個就叫連鎖更新。
將會對性能形成必定的影響。
quicklist快速列表:
快速列表是ziplist和linkedlist的混合體。它將linkedlist按段切分,每一段使用ziplist讓內存緊湊,多個ziplist之間使用雙向指針串接起來。爲了進一步節省空間。Redis還會對ziplist進行壓縮,
使用LZF算法壓縮。能夠選擇壓縮的深度,默認的壓縮深度是0既不壓縮。有時候爲了節省空間,可是又不想由於壓縮而影響取出和放入的性能,能夠選着壓縮深度爲1或者2。
既首尾的第一個或者首尾的第一個和第二個不壓縮。
struct quicklist{ quicklistNode* head;//頭節點 quicklistNode* tail;//尾節點 long count;//元素總數 int nodes;//ziplist 節點數量 int compressDepth;//LZF 算法壓縮深度 }; struct quicklistNode{ quicklistNode* prev;//前一個節點 quicklistNode* next;//下一個節點 ziplist* zl;//指向壓縮列表的指針 int32 size;//壓縮列表的字節總數 int16 count;//壓縮列表中的元素個數 int2 encoding;//存儲形式 2bit 是原生字節數組仍是被壓縮過的 };
注:LZF算法是蘋果開源的一種無損壓縮算法,有興趣的看官們能夠自行去了解下,這裏不作過多的贅述。
3.字典(dictht)
字典又稱之爲hash,或者映射(map),也能夠理解爲redis本身實現的JDK1.7版本的HashMap。是一種用於保存鍵值對的抽象數據結構。在字典中,一個鍵(Key)能夠和一個值(value)進行關聯,成爲一個鍵值對。
字典中每一個鍵都是惟一的。程序能夠在字典中根據鍵查找與之關聯的值,或者經過鍵來跟新或者刪除值。接下來的內容將詳細介紹Redis中字典的兩種底層實現。
第一種:ziplist
當字典中的元素知足如下兩個條件時,字典的底層將會使用ziplist來報錯鍵值對。
1.字典對象保存的全部鍵值對的鍵和值的字符串長度都小於64個字節。
2.字段對象保存的鍵值對數量小於512個。
看到這裏也許有的看官會不明白了。在上面咱們剛剛學過ziplist壓縮列表,你們都知道這其實就是一個數組。前面的列表能夠用數組來保存,可是這裏是鍵值對啊,一個map,怎麼用數組來保存?
各位看官先莫慌,按照慣例先上圖(畢竟無圖無真相啊):
第二種:hash表
hash表顧名思義,其本質就是一個HashMap。下面我帶各位看官們看看他的結構
typedef struct dict{ dictType *type;//類型特定函數 void *privdata;//私有函數 dictht ht[2];//hash表 int trehashidx;//擴容索引 當不在擴容的時候 爲-1 }; typedef struct dictht{ dictEntry **table;//哈希表數組 unsigned long size;//哈希表大小 unsigned long sizemask;//哈希表槽位取模基準參數 老是等於size - 1 unsigned long used;//已有節點數量 } typedef struct dictEntry{ void *key;//鍵 //值 這裏三個屬性是由於 值多是一個對象引用也多是 一個uint64_t或者int64_t整數值 union{ void *val; uint64_t u64; int64_t s64; } v; //下一個節點 多個槽位相同的值 串聯成一個鏈表 struct dictEntry *next; }
結構示意圖:
漸進式rehash :
字典在擴容的過程當中會在 ht[1] 建立一個新的哈希表,並且它並不會一次性將全部的數據都轉移到新的哈希表之中。而是分而治之,像螞蟻搬家同樣,一部分一部分的遷移,咱們稱之爲漸進式rehash。
由於篇幅緣由,後面會寫一篇專門的文章來詳細說明漸進式rehash和在遷移過程當中獲取元素的方式,這裏就簡略的介紹一下。
4.集合(sets)
Redis集合中的Set集合,至關與java中的HashSet,它內部的鍵值對是無序的,惟一的。在Redis中Set集合底層也存在兩種實現方式。
第一種,當一個集合只包含整數值元素,而且這個集合的元素數量不超過512時,Redis就會使用整數集合做爲集合鍵的底層實現。
typedef struct intset{ //編碼方式 uint32_t encoding; //集合包含的元素數量 uint32_t length; //保存元素的數組 int8_t contents[]; };
contents數組是整數集合的底層實現:整數集合的每一個元素都是contents數組的一個數組項(item),各個項在數組中按值的大小從小到大有序地排列,而且數組中不包含任何重複項。
length屬性記錄了整數集合包含的元素數量,也便是contents數組的長度。雖然intset結構將contents數組聲明爲int8_t類型的數組。但實際上contents數組的真正類型取決於encoding;
若是encoding類型爲INTSET_ENC_INT16,那麼contents就是一個int16_t類型的數組。
若是encoding類型爲INTSET_ENC_INT32,那麼contents就是一個int32_t類型的數組。
若是encoding類型爲INTSET_ENC_INT64,那麼contents就是一個int64_t類型的數組。
整數數組的升級:
當咱們要將一個新的元素添加到集合中,而且新元素的類型比整數集合現有的全部元素類型都要長時。整數集合現有先進行升級,而後才能將新元素添加到整數集合裏。
好比向一個包含1,2,3 的數組中插入一個65535;
第二種使用字典實現,字典的每一個鍵都是一個字符串對象,而值則所有被設置爲Null;
5.有序集合(ZSet)
ZSet在Redis底層也存在兩種實現,一種是簡單實現經過Ziplist保存元素成員。
結構以下圖所示:
還一種是複雜模型,它內部保存有一個跳錶和一個字典,經過字典來實現O(1)時間複雜度的元素查找,經過跳錶來完成高性能的zrank、zrange等範圍命令。若是單純的字典,要完成zrange命令,
要先將全部數據排序,時間複雜度爲O(nlogn),並且還須要長度爲N的數組來保存排序完成的數據。若是單純使用跳錶,查詢的時間複雜度爲O(logn)。
結構以下圖所示:
總結:
這五種只是最經常使用的五種數據結構,在Redis中還有其餘類型的數據結構或者實現。好比緊湊列表listpack,基數樹rax等還等待着咱們去探索。
因爲篇幅有限,這期就先到這裏,預知後事如何,請聽下集分解...
下集來了,本系列其餘文章:
小白也能看懂的REDIS教學基礎篇——朋友面試被SKIPLIST跳躍表攔住了
參考書籍:
《Reids設計與實現》
《Redis深度歷險——核心原理與應用實踐》