鍊錶提供了高效的節點重排能力,以及順序性的節點訪問方式,並且可以通過增刪節點來靈活地調整鍊錶的長度。鍊錶是一種非常常見的資料結構。由於 redis 使用的 c 語言並沒有這種資料結構,因此,作者在 redis 對這一資料結構進行了實現。redis 的鍊錶實現為雙向鍊錶,主要用在實現列表鍵、發布訂閱、儲存多客戶端狀態、伺服器模組,訂閱模組和儲存輸入命令等方面,使用較廣。
redis 原始碼中關於 adlist 的部分,主要在adlist.h
和adlist.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); // 鍊錶旋轉
建立 adlistlist *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可以重複,就和乙個班裡的同學學號不能重複,但是考試成績可以相同。新增元素 命令為 ...