Redis基本資料結構之雙向鍊錶

2021-09-13 12:08:30 字數 4149 閱讀 1186

鍊錶提供了高效的節點重排能力,以及順序性的節點訪問方式,並且可以通過增刪節點來靈活地調整鍊錶的長度。
鍊錶是一種非常常見的資料結構。由於 redis 使用的 c 語言並沒有這種資料結構,因此,作者在 redis 對這一資料結構進行了實現。redis 的鍊錶實現為雙向鍊錶,主要用在實現列表鍵、發布訂閱、儲存多客戶端狀態、伺服器模組,訂閱模組和儲存輸入命令等方面,使用較廣。

redis 原始碼中關於 adlist 的部分,主要在adlist.hadlist.c這兩個檔案中。

首先在adlist.h中找到定義

// list 節點

typedef struct listnode listnode;

// redis 雙鏈表實現

typedef struct list list;

可以發現,這就是乙個無環雙向鍊錶。

list結構中帶有乙個len的變數,可以將獲取鍊錶長度的時間複雜度從 o(n) 降到 o(1)。

head指標和tail指標讓給我們可以快速的找到鍊錶的頭尾,時間複雜度都是 o(1)。

三個函式指標,讓我們可以對鍊錶有更靈活的操作,使用起來也更加方便。

當需要進行鍊錶迭代時,可以使用如下函式:

typedef struct listiter  listiter;
direction決定了遍歷的方向,可正向可反向。

這部分定義了一些獲取list結構的巨集,簡化操作。

#define listlength(l) ((l)->len)                    // 獲取 list 中包含的 node 數量

#define listfirst(l) ((l)->head) // 獲取 list 頭節點指標

#define listlast(l) ((l)->tail) // 獲取 list 尾節點指標

#define listprevnode(n) ((n)->prev) // 獲取當前節點的前驅節點

#define listnextnode(n) ((n)->next) // 獲得當前節點的後繼節點

#define listnodevalue(n) ((n)->value)

#define listsetdupmethod(l,m) ((l)->dup = (m)) // 指定節點複製函式

#define listsetfreemethod(l,m) ((l)->free = (m)) // 指定節點釋放函式

#define listsetmatchmethod(l,m) ((l)->match = (m)) // 指定節點的比較函式

#define listgetdupmethod(l) ((l)->dup) // 獲得節點複製函式

#define listgetfree(l) ((l)->free)

#define listgetmatchmethod(l) ((l)->match)

這部分定義了一些雙向鍊錶的常用操作。

list *listcreate(void); // 建立乙個不包含任何節點的新鍊錶

void listrelease(list *list); // 釋放給定鍊錶,以及鍊錶中的所有節點

// crud 操作

list *listaddnodehead(list *list, void *value); // 頭部插入節點

list *listaddnodetail(list *list, void *value); // 尾部插入節點

list *listinsertnode(list *list, listnode *old_node, void *value, int after); // 中間某個位置插入節點

void listdelnode(list *list, listnode *node); // o(n) 刪除指定節點

listiter *listgetiterator(list *list, int direction); // 獲取指定迭代器

void listreleaseiterator(listiter *iter); // 釋放迭代器

listnode *listnext(listiter *iter); // 迭代下乙個節點

list *listdup(list *orig); // 鍊錶複製

listnode *listsearchkey(list *list, void *key); // o(n) 按 key 找節點

listnode *listindex(list *list, long index); // o(n)

void listrewind(list *list, listiter *li); // 重置為正向迭代器

void listrewindtail(list *list, listiter *li); // 重置為逆向迭代器

void listrotate(list *list); // 鍊錶旋轉

建立 adlist
list *listcreate(void)

建立乙個空的 adlist 很簡單,就是分配記憶體,初始化資料結構,而listrelease的釋放鍊錶過程與之相反,這個自不必多說。

adlist 的 crud 操作

首先是插入資料,分三種情況:頭部插入、中間插入和尾部插入。

(1) 頭部插入

// 頭部插入值 value

list *listaddnodehead(list *list, void *value)

else

list->len++; // 更新鍊錶長度資訊

return list;

}

(2)尾部插入節點類似,就不囉嗦了。

(3)中間插入

// 在 list 指定節點 old_node 後(after=1)或前插入乙個節點

list *listinsertnode(list *list, listnode *old_node, void *value, int after)

} else

}// 處理 node 相鄰兩個節點的指向

if (node->prev != null)

if (node->next != null)

list->len++;

return list;

}

然後是刪除操作。

// 從 list 中刪除 node 節點

void listdelnode(list *list, listnode *node)

最後是查詢。

// 從 list 中查詢 key

listnode *listsearchkey(list *list, void *key)

} else }}

return null;

}

// 獲得 list 中第 index 個節點,index 為負數表示從尾部倒序往前找

listnode *listindex(list *list, long index) else

return n;

}

其他

迭代器實現如下:

listiter *listgetiterator(list *list, int direction)

另外,乙個旋轉 list 的操作,實現效果將 1 → 2 → 3 → 4 變成 4 → 1 → 2 → 3

void listrotate(list *list)
adlist 其實就是把雙向鍊錶的基本操作實現了一遍,看了一遍相當於複習了一遍(之前面試總問這些,哈哈),不過作者設計的很巧,值得學習。

Redis原始碼學習3 基本資料結構之雙向鍊錶

楔子雙向鍊錶定義 基本函式 listcreate建立列表 listrelease釋放列表 listaddnodehead在表頭新增節點 listaddnodetail在表尾新增節點 listinsertnode在指定節點前後插入節點 listdelnode刪除鍊錶中指定節點 迭代器及相關函式 迭代器...

redis基本資料結構之ZSet

zset資料結構類似於set結構,只是zset結構中,每個元素都會有乙個分值,然後所有元素按照分值的大小進行排列,相當於是乙個進行了排序的鍊錶。如果zset是乙個鍊錶,而且內部元素是有序的,在進行元素插入和刪除,以及查詢的時候,就必須要遍歷鍊錶才行,時間複雜度就達到了o n 這個在以單執行緒處理的r...

Redis基本資料結構之ZSet

zset保留了集合不能有重複成員的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下標作為排序依據不同的是,它給每個元素設定乙個分數 score 作為排序的依據。有序集合中的元素不能重複,但是score可以重複,就和乙個班裡的同學學號不能重複,但是考試成績可以相同。新增元素 命令為 ...