1.介紹
Redis中List是通過ListNode構造的雙向連結串列。
特點:
1.雙端:獲取某個結點的前驅和後繼結點都是O(1)
2.無環:表頭的prev指標和表尾的next指標都指向NULL,對連結串列的訪問都是以NULL為終點
3.帶表頭指標和表尾指標:獲取表頭和表尾的複雜度都是O(1)
4.帶連結串列長度計數器:len屬性記錄,獲取連結串列長度O(1)
5.多型:連結串列結點使用void*指標來儲存結點的值,並且可以通過連結串列結構的三個函式為結點值設定型別特定函式,所以連結串列可以儲存各種不同型別的值
雙向連結串列詳解:https://www.cnblogs.com/vic-tory/p/13140779.html
中文網:http://redis.cn/commands.html#list
2.操作命令
Lpush——先進後出,在列表頭部插入元素
Rpush——先進先出,在列表的尾部插入元素
Lrange——出棧,根據索引,獲取列表元素
Lpop——左邊出棧,獲取列表的第一個元素
Rpop——右邊出棧,獲取列表的最後一個元素
Lindex——根據索引,取出元素
Llen——連結串列長度,元素個數
Lrem——根據key,刪除n個value
Ltrim——根據索引,刪除指定元素
Rpoplpush——出棧,入棧
Lset——根據index,設定value
Linsert before——根據value,在之前插入值
Linsert after——根據value,在之後插入值
127.0.0.1:6379> lpush mylist "world" (integer) 1 127.0.0.1:6379> rpush mylist "!" (integer) 2 127.0.0.1:6379> llen mylist (integer) 2 127.0.0.1:6379> lpop mylist "world" 127.0.0.1:6379> rpop mylist "!" 127.0.0.1:6379> lpush mylist "Hello" (integer) 1 127.0.0.1:6379> lpush mylist "world" (integer) 2 127.0.0.1:6379> lset mylist 1 World OK
3.原始碼解析
// listNode 雙端連結串列節點 typedef struct listNode { // 前置節點 struct listNode *prev; // 後置節點 struct listNode *next; // 節點的值 void *value; } listNode;
// list 雙端連結串列 typedef struct list { // 在c語言中,用結構體的方式來模擬物件是一種常見的手法 // 表頭節點 listNode *head; // 表尾節點 listNode *tail; // 節點值複製函式 void *(*dup)(void *ptr); // 節點值釋放函式 void(*free)(void *ptr); // 節點值對比函式 int(*match)(void *ptr, void *key); // 連結串列所包含的節點數量 unsigned long len; } list;
/* 作為巨集實現的函式 */ //獲取長度 #define listLength(l) ((l)->len) //獲取頭節點 #define listFirst(l) ((l)->head) //獲取尾結點 #define listLast(l) ((l)->tail) //獲取前一個結點 #define listPrevNode(n) ((n)->prev) //獲取後一個結點 #define listNextNode(n) ((n)->next) //獲取結點的值 是一個void型別指標 #define listNodeValue(n) ((n)->value) /* 下面3個函式主要用來設定list結構中3個函式指標,引數m為method的意思 */ #define listSetDupMethod(l,m) ((l)->dup = (m)) #define listSetFreeMethod(l,m) ((l)->free = (m)) #define listSetMatchMethod(l,m) ((l)->match = (m)) /* 下面3個函式主要用來獲取list結構的單個函式指標 */ #define listGetDupMethod(l) ((l)->dup) #define listGetFree(l) ((l)->free) #define listGetMatchMethod(l) ((l)->match)
3.API實現
listCreate函式:建立一個不包含任何結點的新連結串列
/* * listCreate 建立一個新的連結串列 * * 建立成功返回連結串列,失敗返回 NULL 。 * * T = O(1) */ list *listCreate(void) { struct list *list; // 分配記憶體 if ((list = zmalloc(sizeof(*list))) == NULL) return NULL;//記憶體分配失敗則返回NULL // 初始化屬性 list->head = list->tail = NULL;//空連結串列 list->len = 0; list->dup = NULL; list->free = NULL; list->match = NULL; return list; }
listAddNodeHead函式:將一個包含給定值的新結點新增到給定連結串列的表頭
/* * listAddNodeHead 將一個包含有給定值指標 value 的新節點新增到連結串列的表頭 * * 如果為新節點分配記憶體出錯,那麼不執行任何動作,僅返回 NULL * * 如果執行成功,返回傳入的連結串列指標 * * T = O(1) */ list *listAddNodeHead(list *list, void *value) { listNode *node; // 為節點分配記憶體 if ((node = zmalloc(sizeof(*node))) == NULL) return NULL; // 儲存值指標 node->value = value; // 新增節點到空連結串列 if (list->len == 0) { list->head = list->tail = node; //該結點的前驅和後繼都為NULL node->prev = node->next = NULL; } else { // 新增節點到非空連結串列 node->prev = NULL; node->next = list->head; list->head->prev = node; list->head = node; } // 更新連結串列節點數 list->len++; return list; }
listAddNodeTail函式:將一個包含給定值的新結點插入到給定連結串列的表尾
/* * listAddNodeTail 將一個包含有給定值指標 value 的新節點新增到連結串列的表尾 * * 如果為新節點分配記憶體出錯,那麼不執行任何動作,僅返回 NULL * * 如果執行成功,返回傳入的連結串列指標 * * T = O(1) */ list *listAddNodeTail(list *list, void *value) { listNode *node; // 為新節點分配記憶體 if ((node = zmalloc(sizeof(*node))) == NULL) return NULL; // 儲存值指標 node->value = value; // 目標連結串列為空 if (list->len == 0) { list->head = list->tail = node; node->prev = node->next = NULL; }//目標鏈非空 else { node->prev = list->tail; node->next = NULL; list->tail->next = node; list->tail = node; } // 更新連結串列節點數 list->len++; return list; }
listInsertNode函式:將一個給定值的新結點插入到給定結點之前或者之後
/* * listInsertNode 建立一個包含值 value 的新節點,並將它插入到 old_node 的之前或之後 * * 如果 after 為 0 ,將新節點插入到 old_node 之前。 * 如果 after 為 1 ,將新節點插入到 old_node 之後。 * * T = O(1) */ list *listInsertNode(list *list, listNode *old_node, void *value, int after) { listNode *node; // 建立新節點 if ((node = zmalloc(sizeof(*node))) == NULL) return NULL; // 儲存值 node->value = value; // 將新節點新增到給定節點之後 if (after) { node->prev = old_node; node->next = old_node->next; // 給定節點是原表尾節點 if (list->tail == old_node) { list->tail = node; } } // 將新節點新增到給定節點之前 else { node->next = old_node; node->prev = old_node->prev; // 給定節點是原表頭節點 if (list->head == old_node) { list->head = node; } } // 更新新節點的前置指標 if (node->prev != NULL) { node->prev->next = node; } // 更新新節點的後置指標 if (node->next != NULL) { node->next->prev = node; } // 更新連結串列節點數 list->len++; return list; }
listDelNode函式:從指定的list中刪除給定的結點
/* * listDelNode 從連結串列 list 中刪除給定節點 node * * 對節點私有值(private value of the node)的釋放工作由呼叫者進行。該函式一定會成功. * * T = O(1) */ void listDelNode(list *list, listNode *node) { // 調整前置節點的指標 if (node->prev) node->prev->next = node->next; else list->head = node->next; // 調整後置節點的指標 if (node->next) node->next->prev = node->prev; else list->tail = node->prev; // 釋放值 if (list->free) list->free(node->value); // 釋放節點 zfree(node); // 連結串列數減一 list->len--; }
listRelease函式:釋放給定連結串列以及連結串列中所有結點
/* * listRelease 釋放整個連結串列,以及連結串列中所有節點, 這個函式不可能會失敗. * * T = O(N) */ void listRelease(list *list) { unsigned long len; listNode *current, *next; // 指向頭指標 current = list->head; // 遍歷整個連結串列 len = list->len; while (len--) { next = current->next; // 如果有設定值釋放函式,那麼呼叫它 if (list->free) list->free(current->value); // 釋放節點結構 zfree(current); current = next; } // 釋放連結串列結構 zfree(list); }
該函式不僅釋放了表結點的記憶體還釋放了表結構的記憶體
listGetIterator函式:為給定連結串列建立一個迭代器
在講這個函式之前,我們應該先看看連結串列迭代器的結構:
// listIter 雙端連結串列迭代器 typedef struct listIter { // 當前迭代到的節點 listNode *next; // 迭代的方向 int direction; } listIter;
迭起器只有兩個重要的屬性:當前迭代到的結點,迭代的方向
下面再看看連結串列的迭代器建立函式
/* * listGetIterator 為給定連結串列建立一個迭代器, * 之後每次對這個迭代器呼叫 listNext 都返回被迭代到的連結串列節點,呼叫該函式不會失敗 * * direction 引數決定了迭代器的迭代方向: * AL_START_HEAD :從表頭向表尾迭代 * AL_START_TAIL :從表尾想表頭迭代 * * T = O(1) */ listIter *listGetIterator(list *list, int direction) { // 為迭代器分配記憶體 listIter *iter; if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL; // 根據迭代方向,設定迭代器的起始節點 if (direction == AL_START_HEAD) iter->next = list->head; else iter->next = list->tail; // 記錄迭代方向 iter->direction = direction; return iter; }
listReleaseIterator函式:釋放指定的迭代器
/* * listReleaseIterator 釋放迭代器 * * T = O(1) */ void listReleaseIterator(listIter *iter) { zfree(iter); }
listRewind函式和listRewindTail函式:迭代器重新指向表頭或者表尾的函式
/* * 將迭代器的方向設定為 AL_START_HEAD, * 並將迭代指標重新指向表頭節點。 * * T = O(1) */ void listRewind(list *list, listIter *li) { li->next = list->head; li->direction = AL_START_HEAD; } /* * 將迭代器的方向設定為 AL_START_TAIL, * 並將迭代指標重新指向表尾節點。 * * T = O(1) */ void listRewindTail(list *list, listIter *li) { li->next = list->tail; li->direction = AL_START_TAIL; }
listNext函式:返回當前迭代器指向的結點
/* * 返回迭代器當前所指向的節點。 * * 刪除當前節點是允許的,但不能修改連結串列裡的其他節點。 * * 函式要麼返回一個節點,要麼返回 NULL,常見的用法是: * * iter = listGetIterator(list,<direction>); * while ((node = listNext(iter)) != NULL) { * doSomethingWith(listNodeValue(node)); * } * * T = O(1) */ listNode *listNext(listIter *iter) { listNode *current = iter->next; if (current != NULL) { // 根據方向選擇下一個節點 if (iter->direction == AL_START_HEAD) // 儲存下一個節點,防止當前節點被刪除而造成指標丟失 iter->next = current->next; else // 儲存下一個節點,防止當前節點被刪除而造成指標丟失 iter->next = current->prev; } return current; }
該函式保持了當前結點的下一個結點,避免了當前結點被刪除而迭代器無法繼續迭代的尷尬情況
listDup函式:複製整個連結串列,返回副本
/* * 複製整個連結串列。 * * 複製成功返回輸入連結串列的副本, * 如果因為記憶體不足而造成複製失敗,返回 NULL 。 * * 如果連結串列有設定值複製函式 dup ,那麼對值的複製將使用複製函式進行, * 否則,新節點將和舊節點共享同一個指標。 * * 無論複製是成功還是失敗,輸入節點都不會修改。 * * T = O(N) */ list *listDup(list *orig) { list *copy;//連結串列副本 listIter *iter;//連結串列迭代器 listNode *node;//連結串列結點 // 建立新的空連結串列 if ((copy = listCreate()) == NULL) return NULL;//建立空的連結串列失敗則返回NULL // 設定副本連結串列的節點值處理函式 copy->dup = orig->dup; copy->free = orig->free; copy->match = orig->match; //獲取輸入連結串列的迭代器 iter = listGetIterator(orig, AL_START_HEAD); //遍歷整個輸入連結串列進行復制 while ((node = listNext(iter)) != NULL) { //副本結點值 void *value; // 存在複製函式 if (copy->dup) { //呼叫複製函式複製 value = copy->dup(node->value); //複製結果為空,說明覆制失敗 if (value == NULL) { //複製失敗則釋放副本連結串列和迭代器,避免記憶體洩漏 listRelease(copy); listReleaseIterator(iter); return NULL; } } //不存在複製函式 則直接指標指向 else value = node->value; // 將節點新增到副本連結串列 if (listAddNodeTail(copy, value) == NULL) { //如果不能成功新增,則釋放副本連結串列和迭代器,避免記憶體洩漏 listRelease(copy); listReleaseIterator(iter); return NULL; } } // 釋放迭代器 listReleaseIterator(iter); // 返回副本 return copy; }
如果複製失敗則要注意釋放副本連結串列和迭代器,避免記憶體洩漏
listSearchKey函式:查詢list中值和key匹配的結點
/* * 查詢連結串列 list 中值和 key 匹配的節點。 * * 對比操作由連結串列的 match 函式負責進行, * 如果沒有設定 match 函式, * 那麼直接通過對比值的指標來決定是否匹配。 * * 如果匹配成功,那麼第一個匹配的節點會被返回。 * 如果沒有匹配任何節點,那麼返回 NULL 。 * * T = O(N) */ listNode *listSearchKey(list *list, void *key) { listIter *iter;//連結串列迭代器 listNode *node;//連結串列結點 //獲得連結串列迭代器 iter = listGetIterator(list, AL_START_HEAD); //遍歷整個連結串列查詢 while ((node = listNext(iter)) != NULL) { //存在比較函式 if (list->match) { //利用比較函式進行比較 if (list->match(node->value, key)) { //返回目標結點之前釋放迭代器空間,避免記憶體洩漏 listReleaseIterator(iter); return node; } } //不存在比較函式 else { //直接比較 if (key == node->value) { //返回目標結點之前釋放迭代器空間,避免記憶體洩漏 listReleaseIterator(iter); // 找到 return node; } } } //返回目標結點之前釋放迭代器空間,避免記憶體洩漏 listReleaseIterator(iter); // 未找到 return NULL; }
listIndex函式:返回連結串列在給定索引上的值
/* * 返回連結串列在給定索引上的值。 * * 索引以 0 為起始,也可以是負數, -1 表示連結串列最後一個節點,諸如此類。 * * 如果索引超出範圍(out of range),返回 NULL 。 * * T = O(N) */ listNode *listIndex(list *list, long index) { listNode *n;//連結串列結點 /* n不用設定成NULL的原因: 如果索引超出範圍, 那肯定是找到表頭或者表尾沒有找到, 表頭的前驅和表尾的後繼都是NULL, 所以這裡n不用設定為NULL,直接設定也可以*/ // 如果索引為負數,從表尾開始查詢 if (index < 0) { //變成正數,方便索引 index = (-index) - 1; //從尾部開始找 n = list->tail; //尋找 因為從尾部開始找,所以是前驅 while (index-- && n) n = n->prev; } // 如果索引為正數,從表頭開始查詢 else { //從頭部開始找 n = list->head; //尋找 因為從頭部開始找,所以是後繼 while (index-- && n) n = n->next; } return n; }
listRotate函式:取出連結串列的表尾結點放到表頭,成為新的表頭結點
/* * 取出連結串列的表尾節點,並將它移動到表頭,成為新的表頭節點。 * * T = O(1) */ void listRotate(list *list) { //表尾結點 listNode *tail = list->tail; //如果連結串列中只有一個元素,那麼表頭就是表尾,可以直接返回 if (listLength(list) <= 1) return; // 重新設定表尾節點 list->tail = tail->prev; list->tail->next = NULL; // 插入到表頭 list->head->prev = tail; tail->prev = NULL; tail->next = list->head; list->head = tail; }