鏈表
鏈表在Redis中的應用非常廣泛,比如列表鍵的底層實現之一就是鏈表。當一個列表鍵包含了數量比較多的元素,又或者列表中包含的元素都是比較長的字符串時,Redis就會使用鏈表作爲列表鍵的底層實現。
除了鏈表鍵之外, 發佈與訂閱、慢查詢、監視器等功能也用到了鏈表, Redis 服務器本身還使用鏈表來保存多個客戶端的狀態信息, 以及使用鏈表來構建客戶端輸出緩衝區(output buffer)。
每個鏈表節點使用一個 adlist.h/listNode
結構來表示:
typedef struct listNode {
// 前置節點
struct listNode *prev;
// 後置節點
struct listNode *next;
// 節點的值
void *value;
} 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
函數則用於對比鏈表節點所保存的值和另一個輸入值是否相等。
特性總結
-
雙端: 鏈表節點帶有
prev
和next
指針, 獲取某個節點的前置節點和後置節點的複雜度都是 。 -
無環: 表頭節點的
prev
指針和表尾節點的next
指針都指向NULL
, 對鏈表的訪問以NULL
爲終點。 -
帶表頭指針和表尾指針: 通過
list
結構的head
指針和tail
指針, 程序獲取鏈表的表頭節點和表尾節點的複雜度爲 。 -
帶鏈表長度計數器: 程序使用
list
結構的len
屬性來對list
持有的鏈表節點進行計數, 程序獲取鏈表中節點數量的複雜度爲 。 -
多態: 鏈表節點使用
void*
指針來保存節點值, 並且可以通過list
結構的dup
、free
、match
三個屬性爲節點值設置類型特定函數, 所以鏈表可以用於保存各種不同類型的值。
鏈表和鏈表節點 API
函數 | 作用 | 時間複雜度 |
---|---|---|
listSetDupMethod |
將給定的函數設置爲鏈表的節點值複製函數。 | 。 |
listGetDupMethod |
返回鏈表當前正在使用的節點值複製函數。 | 複製函數可以通過鏈表的 dup 屬性直接獲得, |
listSetFreeMethod |
將給定的函數設置爲鏈表的節點值釋放函數。 | 。 |
listGetFree |
返回鏈表當前正在使用的節點值釋放函數。 | 釋放函數可以通過鏈表的 free 屬性直接獲得, |
listSetMatchMethod |
將給定的函數設置爲鏈表的節點值對比函數。 | |
listGetMatchMethod |
返回鏈表當前正在使用的節點值對比函數。 | 對比函數可以通過鏈表的 match 屬性直接獲得, |
listLength |
返回鏈表的長度(包含了多少個節點)。 | 鏈表長度可以通過鏈表的 len 屬性直接獲得, 。 |
listFirst |
返回鏈表的表頭節點。 | 表頭節點可以通過鏈表的 head 屬性直接獲得, 。 |
listLast |
返回鏈表的表尾節點。 | 表尾節點可以通過鏈表的 tail 屬性直接獲得, 。 |
listPrevNode |
返回給定節點的前置節點。 | 前置節點可以通過節點的 prev 屬性直接獲得, 。 |
listNextNode |
返回給定節點的後置節點。 | 後置節點可以通過節點的 next 屬性直接獲得, 。 |
listNodeValue |
返回給定節點目前正在保存的值。 | 節點值可以通過節點的 value 屬性直接獲得, 。 |
listCreate |
創建一個不包含任何節點的新鏈表。 | |
listAddNodeHead |
將一個包含給定值的新節點添加到給定鏈表的表頭。 | |
listAddNodeTail |
將一個包含給定值的新節點添加到給定鏈表的表尾。 | |
listInsertNode |
將一個包含給定值的新節點添加到給定節點的之前或者之後。 | |
listSearchKey |
查找並返回鏈表中包含給定值的節點。 | , N 爲鏈表長度。 |
listIndex |
返回鏈表在給定索引上的節點。 | , N 爲鏈表長度。 |
listDelNode |
從鏈表中刪除給定節點。 | 。 |
listRotate |
將鏈表的表尾節點彈出,然後將被彈出的節點插入到鏈表的表頭, 成爲新的表頭節點。 | |
listDup |
複製一個給定鏈表的副本。 | , N 爲鏈表長度。 |
listRelease |
釋放給定鏈表,以及鏈表中的所有節點。 | , N 爲鏈表長度。 |
總結
-
鏈表被廣泛用於實現 Redis 的各種功能, 比如列表鍵, 發佈與訂閱, 慢查詢, 監視器, 等等。
-
每個鏈表節點由一個
listNode
結構來表示, 每個節點都有一個指向前置節點和後置節點的指針, 所以 Redis 的鏈表實現是雙端鏈表。 -
每個鏈表使用一個
list
結構來表示, 這個結構帶有表頭節點指針、表尾節點指針、以及鏈表長度等信息。 -
因爲鏈表表頭節點的前置節點和表尾節點的後置節點都指向
NULL
, 所以 Redis 的鏈表實現是無環鏈表。 -
通過爲鏈表設置不同的類型特定函數, Redis 的鏈表可以用於保存各種不同類型的值。