redis list

mpsky發表於2021-09-09

連結串列

連結串列提供了高效的節點重排能力, 以及順序性的節點訪問方式, 並且可以透過增刪節點來靈活地調整連結串列的長度。

作為一種常用資料結構, 連結串列內建在很多高階的程式語言裡面, 因為 Redis 使用的 C 語言並沒有內建這種資料結構, 所以 Redis 構建了自己的連結串列實現。

連結串列在 Redis 中的應用非常廣泛, 比如列表鍵的底層實現之一就是連結串列: 當一個列表鍵包含了數量比較多的元素, 又或者列表中包含的元素都是比較長的字串時, Redis 就會使用連結串列作為列表鍵的底層實現。

舉個例子, 以下展示的 integers 列表鍵包含了從 1 到 1024 共一千零二十四個整數:

redis> LLEN integers
(integer) 1024

redis> LRANGE integers 0 10
1) "1"2) "2"3) "3"4) "4"5) "5"6) "6"7) "7"8) "8"9) "9"10) "10"11) "11"

integers 列表鍵的底層實現就是一個連結串列, 連結串列中的每個節點都儲存了一個整數值。

除了連結串列鍵之外, 釋出與訂閱、慢查詢、監視器等功能也用到了連結串列, Redis 伺服器本身還使用連結串列來儲存多個客戶端的狀態資訊, 以及使用連結串列來構建客戶端輸出緩衝區(output buffer), 本書後續的章節將陸續對這些連結串列應用進行介紹。

本章接下來的內容將對 Redis 的連結串列實現進行介紹, 並列出相應的連結串列和連結串列節點 API 。

因為已經有很多優秀的演算法書籍對連結串列的基本定義和相關演算法進行了詳細的講解, 所以本章不會介紹這些內容, 如果不具備關於連結串列的基本知識的話, 可以參考《》一書的 3.3 至 3.5 節, 或者《》一書的 3.2 節, 又或者《》一書的 10.2 節。

連結串列和連結串列節點的實現

每個連結串列節點使用一個 adlist.h/listNode 結構來表示:

typedef struct listNode {

    // 前置節點
    struct listNode *prev;

    // 後置節點
    struct listNode *next;

    // 節點的值
    void *value;

} listNode;

多個 listNode 可以透過 prev 和 next 指標組成雙端連結串列, 如圖 3-1 所示。


圖片描述


雖然僅僅使用多個 listNode 結構就可以組成連結串列, 但使用 adlist.h/list 來持有連結串列的話, 操作起來會更方便:

typedef struct list {

    // 表頭節點
    listNode *head;    // 表尾節點
    listNode *tail;    // 連結串列所包含的節點數量
    unsigned long len;    // 節點值複製函式
    void *(*dup)(void *ptr);    // 節點值釋放函式
    void (*free)(void *ptr);    // 節點值對比函式
    int (*match)(void *ptr, void *key);

} list;

list 結構為連結串列提供了表頭指標 head 、表尾指標 tail , 以及連結串列長度計數器 len , 而 dup 、 free 和 match 成員則是用於實現多型連結串列所需的型別特定函式:

  • dup 函式用於複製連結串列節點所儲存的值;

  • free 函式用於釋放連結串列節點所儲存的值;

  • match 函式則用於對比連結串列節點所儲存的值和另一個輸入值是否相等。

圖 3-2 是由一個 list 結構和三個 listNode 結構組成的連結串列:


圖片描述


Redis 的連結串列實現的特性可以總結如下:

  • 雙端: 連結串列節點帶有 prev 和 next 指標, 獲取某個節點的前置節點和後置節點的複雜度都是 O(1) 。

  • 無環: 表頭節點的 prev 指標和表尾節點的 next 指標都指向 NULL , 對連結串列的訪問以 NULL 為終點。

  • 帶表頭指標和表尾指標: 透過 list 結構的 head 指標和 tail 指標, 程式獲取連結串列的表頭節點和表尾節點的複雜度為 O(1) 。

  • 帶連結串列長度計數器: 程式使用 list 結構的 len 屬性來對 list 持有的連結串列節點進行計數, 程式獲取連結串列中節點數量的複雜度為 O(1) 。

  • 多型: 連結串列節點使用 void* 指標來儲存節點值, 並且可以透過 list 結構的 dup 、 free 、 match 三個屬性為節點值設定型別特定函式, 所以連結串列可以用於儲存各種不同型別的值。

連結串列和連結串列節點的 API

表 3-1 列出了所有用於操作連結串列和連結串列節點的 API 。


表 3-1 連結串列和連結串列節點 API

函式 作用 時間複雜度
listSetDupMethod 將給定的函式設定為連結串列的節點值複製函式。 O(1)  。
listGetDupMethod 返回連結串列當前正在使用的節點值複製函式。 複製函式可以透過連結串列的 dup 屬性直接獲得, O(1)
listSetFreeMethod 將給定的函式設定為連結串列的節點值釋放函式。 O(1) 。
listGetFree 返回連結串列當前正在使用的節點值釋放函式。 釋放函式可以透過連結串列的 free 屬性直接獲得, O(1)
listSetMatchMethod 將給定的函式設定為連結串列的節點值對比函式。 O(1)
listGetMatchMethod 返回連結串列當前正在使用的節點值對比函式。 對比函式可以透過連結串列的 match 屬性直接獲得, O(1)
listLength 返回連結串列的長度(包含了多少個節點)。 連結串列長度可以透過連結串列的 len 屬性直接獲得, O(1) 。
listFirst 返回連結串列的表頭節點。 表頭節點可以透過連結串列的 head 屬性直接獲得, O(1) 。
listLast 返回連結串列的表尾節點。 表尾節點可以透過連結串列的 tail 屬性直接獲得, O(1) 。
listPrevNode 返回給定節點的前置節點。 前置節點可以透過節點的 prev 屬性直接獲得, O(1) 。
listNextNode 返回給定節點的後置節點。 後置節點可以透過節點的 next 屬性直接獲得, O(1) 。
listNodeValue 返回給定節點目前正在儲存的值。 節點值可以透過節點的 value 屬性直接獲得, O(1) 。
listCreate 建立一個不包含任何節點的新連結串列。 O(1)
listAddNodeHead 將一個包含給定值的新節點新增到給定連結串列的表頭。 O(1)
listAddNodeTail 將一個包含給定值的新節點新增到給定連結串列的表尾。 O(1)
listInsertNode 將一個包含給定值的新節點新增到給定節點的之前或者之後。 O(1)
listSearchKey 查詢並返回連結串列中包含給定值的節點。 O(N) , N 為連結串列長度。
listIndex 返回連結串列在給定索引上的節點。 O(N) , N 為連結串列長度。
listDelNode 從連結串列中刪除給定節點。 O(1) 。
listRotate 將連結串列的表尾節點彈出,然後將被彈出的節點插入到連結串列的表頭, 成為新的表頭節點。 O(1)
listDup 複製一個給定連結串列的副本。 O(N) , N 為連結串列長度。
listRelease 釋放給定連結串列,以及連結串列中的所有節點。 O(N) , N 為連結串列長度。

重點回顧

  • 連結串列被廣泛用於實現 Redis 的各種功能, 比如列表鍵, 釋出與訂閱, 慢查詢, 監視器, 等等。

  • 每個連結串列節點由一個 listNode 結構來表示, 每個節點都有一個指向前置節點和後置節點的指標, 所以 Redis 的連結串列實現是雙端連結串列。

  • 每個連結串列使用一個 list 結構來表示, 這個結構帶有表頭節點指標、表尾節點指標、以及連結串列長度等資訊。

  • 因為連結串列表頭節點的前置節點和表尾節點的後置節點都指向 NULL , 所以 Redis 的連結串列實現是無環連結串列。

  • 透過為連結串列設定不同的型別特定函式, Redis 的連結串列可以用於儲存各種不同型別的值。



作者:Linkerist
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1806/viewspace-2816314/,如需轉載,請註明出處,否則將追究法律責任。

相關文章