Redis 五種數(shù)據(jù)結(jié)構(gòu)

一、SDS 簡單動態(tài)字符串

Redis 在存儲 可能發(fā)生改變、非字面量 的字符串時饶氏,都會使用 SDS芹橡,比如:redis 的 key 和 value 等毒坛。

1. 結(jié)構(gòu)體定義
struct sdshdr{
    // 字符串真是長度
    int len;
    // buf 中空閑的長度
    int free;
    // 存儲字符串的 char 數(shù)組
    char buf[];
}
  • len,字符串真實長度林说。
  • free煎殷,buf 中空閑的長度(與 預(yù)分配惰性釋放 有關(guān))
  • buf,用于存儲字符串的 char 數(shù)組腿箩。
2. SDS 和 C 字符串的區(qū)別
  • SDS 獲取字符串長度的時間復(fù)雜度是 O(1)豪直,C 獲取字符串長度的時間復(fù)雜度是 0(n)
  • 通過 SDS 的 API 操作字符串(末尾追加、截取等)完全避免了 內(nèi)存的溢出和泄露珠移,而 C 字符串則需要手動維護(hù)內(nèi)存弓乙,存在溢出和泄露風(fēng)險
  • SDS 減少了 內(nèi)存分配的次數(shù)
    • 內(nèi)存預(yù)分配SDS 通過 alloc = (len <= 1024) ? 2 * len : len + 1024 的方式钧惧,預(yù)留了一部分內(nèi)存空間暇韧,相比 C 字符串,在 增加字符串長度 的場景上浓瞪,有效的減少了內(nèi)存分配的次數(shù)懈玻。
    • 惰性釋放SDS 在釋放空間時,并不立即釋放空間乾颁,而是 在 free 字段中記錄了【空閑】空間的長度涂乌,等待未來使用。C 字符串則每次都需要手動釋放钮孵。且 SDS 的 API 也支持像 C 字符串那樣的 立即釋放骂倘。
  • 相比 C 字符串,SDS二進(jìn)制安全的(不會像 C 字符串一樣巴席,以為空格是結(jié)尾)

二历涝、鏈表

redis 通過鏈表,實現(xiàn)了 隊列 相關(guān)的操作漾唉。

1. 結(jié)構(gòu)體
  • 鏈表節(jié)點:listNode(雙向)
typedef struct listNode {
    // 前置節(jié)點
    struct listNode *prev;
    // 后置節(jié)點
    struct listNode *next;
    // 節(jié)點值
    void *value;
} listNode;
  • 鏈表:list
typedef stuct list {
    // 頭結(jié)點
    listNode *head;
    // 尾節(jié)點
    listNode *tail;
    // 鏈表長度
    unsigned long len;
    // 節(jié)點復(fù)制函數(shù)
    void *(*dup)(void *ptr);
    // 節(jié)點釋放函數(shù)
    void (*free)(void *ptr);
    // 節(jié)點值對比函數(shù)
    int (*match)(void *ptr, void *key);
} list;
2. 特點
  • 雙向:獲取某個節(jié)點的前置荧库、后置節(jié)點的時間負(fù)責(zé)度是 0(1)
  • 無環(huán)head.prevtail.nextnull
  • 帶頭、尾指針:獲取鏈表的 表頭節(jié)點表尾節(jié)點 的時間復(fù)雜度是 O(1)
3. 部分API
函數(shù) 作用 時間復(fù)雜度
listLength 獲取鏈表長度 0(1)
listFirst 獲取鏈表頭節(jié)點 0(1)
listLast 獲取鏈表尾節(jié)點 0(1)
listPrevNode 獲取指定節(jié)點的前置節(jié)點 0(1)
listNextNode 獲取指定節(jié)點的后置節(jié)點 0(1)
listNodeValue 獲取指定節(jié)點的值 0(1)
listAddNodeHead 設(shè)置新的頭節(jié)點 0(1)
listAddNodeTail 設(shè)置新的尾節(jié)點 0(1)

三赵刑、字典

詳見:
redis 源碼分析(一)HashTable 上篇
redis 源碼分析(二)HashTable 下篇

四分衫、跳躍表

redis 的有序結(jié)合,當(dāng)有序集合的 元素數(shù)量比較多 或者 元素的值是比較長的字符串 時般此,redis 使用跳躍表來實現(xiàn) 有序集合蚪战。

1. 結(jié)構(gòu)體
  • 節(jié)點:zskiplistNode
typedef struct zskiplistNode {
    // 前一個節(jié)點的指針
    struct zskiplistNode *backword;
    // 當(dāng)前節(jié)點的分值
    double score;
    // 當(dāng)前節(jié)點的值對象
    robj *obj;
    struct zskiplistLevel {
        // 下一個節(jié)點的指針
        zskiplistNode *forword;
        // 到下一個節(jié)點的跨度
        unsigned int span;
    } level[];
    
} zskiplistNode;
  • 跳躍表:zskiplist
typedef struct zskiplist {
    // 跳躍表頭節(jié)點
    struct zskiplistNode *head;
    // 跳躍表尾節(jié)點
    struct zskiplistNode *tail;
    // 跳躍表的長度
    unsigned long length;
    // 跳躍表中層數(shù)最大的節(jié)點的層數(shù)
    int level;
} zskiplist;
2. 有序集合的 查找
  • 普通的雙向鏈表節(jié)點:DoubleLinkedListNode
typedef struct DoubleLinkedListNode {
    // 后一個節(jié)點的指針
    struct DoubleLinkedListNode *next;
    // 前一個節(jié)點的指針
    struct DoubleLinkedListNode *prev;
    // 當(dāng)前節(jié)點的分值
    double score;
    // 當(dāng)前節(jié)點的值對象
    robj *obj;
} DoubleLinkedListNode;
  • 普通的雙向鏈表節(jié)點牵现,查找的邏輯就是遍歷,時間復(fù)雜度是 O(n)
    10萬元素跳躍表.png

如上圖邀桑,假設(shè)一個跳躍表存儲了 a1 到 a100001 這 10 萬個元素瞎疼,他們的分?jǐn)?shù)分別是 1.0 到 100001.0

  • 上述跳躍表有 五層
    • 第一層跨度為1,即:1->2->3...
    • 第二層跨度為10壁畸,即:1->11->21...
    • 第三層跨度為100贼急,即:1->101->201...
    • 第四層跨度為1000,即:1->1001->2001...
    • 第五層跨度為10000捏萍,即:1->10001->20001...
  • 查找 65536 的過程:
    1. 第五層 查找 (8次) :1->10001->20001->30001->40001->50001->60001太抓,看到 70001 時發(fā)現(xiàn):比 65536 大,去 第四層 繼續(xù)查找令杈。
    2. 第四層 查找 (6次):61001->62001->63001->64001->65001走敌,看到 66001 時發(fā)現(xiàn):比 65536 大,去 第三層 繼續(xù)查找这揣。
    3. 第三層 查找 (6次):65101->65201->65301->65401->65501悔常,看到 65601 時發(fā)現(xiàn):比 65536 大,去 第二層 繼續(xù)查找给赞。
    4. 第二層 查找 (4次):65511->65521->65531机打,看到 65541 時發(fā)現(xiàn):比 65536 大,去 第一層 繼續(xù)查找片迅。
    5. 第一層 查找 (4次):65532->65533->65534残邀,看到 65535 時發(fā)現(xiàn)目標(biāo),得到結(jié)果柑蛇。
  • 結(jié)論:
    • 跳躍表的節(jié)點芥挣,將普通雙向鏈表的節(jié)點的 next 指針,從1個升級到了多個耻台,以層的方式管理空免。
    • 不同層級的指針,跨度不一樣:第一層就是普通雙向鏈表節(jié)點的 next 指針盆耽。以后的各層蹋砚,跨度不斷增大,以達(dá)到 跳過一些節(jié)點摄杂,進(jìn)行查找的目的坝咐。
    • 指針的層數(shù)越高,對查找的性能優(yōu)化越明顯析恢,即:時間復(fù)雜度從 O(n) 減少到了 O(logN)
3. 其他有序集合操作

其他有序集合操作墨坚,像 范圍查找(正向、逆向)映挂、是否存在 等泽篮,其實都是基于 查找 的結(jié)果盗尸,利用 前置、后置指針 進(jìn)行的遍歷咪辱。

五振劳、整數(shù)集合

當(dāng)集合中的元素都是整數(shù)時椎组,Redis 會使用 整數(shù)集合 作為集合的底層實現(xiàn)

1. 結(jié)構(gòu)體
typedef struct intset {
    // 編碼方式
    uint32_t encoding;
    // 集合長度
    uint32_t length;
    // 集合元素
    int8_t contents[];
} intset;

其中油狂,encoding 指明了 contents 中的數(shù)據(jù)類型:

  • INTSET_ENC_INT16,范圍:[-2^15=-32768, 2^15-1=32767]
  • INTSET_ENC_INT32寸癌,范圍:[-2^31=-2147483648, 2^31-1=2147483647]
  • INTSET_ENC_INT64专筷,范圍:[-2^63, 2^63-1]
2. 編碼升級

當(dāng)一個編碼為更小范圍,如 INTSET_ENC_INT16 蒸苇,的整數(shù)集合磷蛹,要添加一個更大范圍,如 INTSET_ENC_INT32溪烤,的元素時味咳,整體集合的編碼方式需要升級。具體思路是:

  • 根據(jù)新元素的類型和集合的元素數(shù)量檬嘀,擴展集合空間槽驶。
  • 將集合中的其他元素升級,并保證之前的有序順序鸳兽。
  • 將新元素添加到集合中(因為范圍更大掂铐,所以位置一定是:第一個或者最后一個)
3. 升級的好處
  • 集合更加靈活。屏蔽了不同范圍整數(shù)的存儲實現(xiàn)揍异。
  • 更加節(jié)省空間全陨。在有需要的時候再進(jìn)行升級,避免了一開始就使用 INTSET_ENC_INT64 來存儲集合元素衷掷。
4. 不存在降級

升級后的整數(shù)集合辱姨,并不會因為,唯一一個更大范圍的元素被刪除戚嗅,就進(jìn)行降級雨涛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市渡处,隨后出現(xiàn)的幾起案子镜悉,更是在濱河造成了極大的恐慌,老刑警劉巖医瘫,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侣肄,死亡現(xiàn)場離奇詭異,居然都是意外死亡醇份,警方通過查閱死者的電腦和手機稼锅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門吼具,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人矩距,你說我怎么就攤上這事拗盒。” “怎么了锥债?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵陡蝇,是天一觀的道長。 經(jīng)常有香客問我哮肚,道長登夫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任允趟,我火速辦了婚禮恼策,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘潮剪。我一直安慰自己涣楷,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布抗碰。 她就那樣靜靜地躺著狮斗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪改含。 梳的紋絲不亂的頭發(fā)上情龄,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音捍壤,去河邊找鬼骤视。 笑死,一個胖子當(dāng)著我的面吹牛鹃觉,可吹牛的內(nèi)容都是我干的专酗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盗扇,長吁一口氣:“原來是場噩夢啊……” “哼祷肯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起疗隶,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤佑笋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后斑鼻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蒋纬,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蜀备。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片关摇。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖碾阁,靈堂內(nèi)的尸體忽然破棺而出输虱,到底是詐尸還是另有隱情,我是刑警寧澤脂凶,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布宪睹,位于F島的核電站,受9級特大地震影響艰猬,放射性物質(zhì)發(fā)生泄漏横堡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一冠桃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧道宅,春花似錦食听、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至泞当,卻和暖如春迹蛤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背襟士。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工盗飒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人陋桂。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓逆趣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親嗜历。 傳聞我的和親對象是個殘疾皇子宣渗,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容