[-]
楔子雙向鍊錶定義
基本函式
listcreate建立列表
listrelease釋放列表
listaddnodehead在表頭新增節點
listaddnodetail在表尾新增節點
listinsertnode在指定節點前後插入節點
listdelnode刪除鍊錶中指定節點
迭代器及相關函式
迭代器定義
相關函式
listgetiterator迭代器建立
迭代器釋放迭代指標調整函式
listiternext迭代函式
雙向鍊錶為redis
列表型別的實現方法之一,列表型別實現除了用到雙向鍊錶,還有壓縮列表。因為雙向鍊錶占用記憶體較多,所以
redis
優先採用壓縮列表來實現自己的列表型別。壓縮列表後續分析,先看看雙向鍊錶的**。
雙向鍊錶作為乙個基本的資料結構,在很多書上都會有描述了,下面就乙個個函式看看redis
的雙向鍊錶的實現方法。檔案參見
src/adlist.c
和src/adlist.h
。關於這個命名是雙向鍊錶還是雙端鍊錶在huangz1990部落格上還有爭議,哈哈,我就先不管這些概念上的問題了。很多內容參考:
先看下adlist.h
中雙向鍊錶的節點定義:
[cpp]view plain
copy
/** 鍊錶節點
*/typedef
struct
listnode listnode;
節點listnode
定義很常規,乙個前驅節點指標,乙個後繼節點指標,乙個值的指標,值的型別為
void *
,可以儲存各種型別。
雙向鍊錶本身的定義如下:
[cpp]view plain
copy
/** 鍊錶
*/typedef
struct
list list;
即定義了煉表頭指標,尾指標,長度以及複製,釋放和匹配的函式指標。
實際的鍊錶結構圖如下:
圖1 redis雙向鍊錶結構示意圖(取自huangz1990部落格)
正是因為有表頭、表尾指標以及鍊錶長度記錄,所以redis在表頭和表尾插入資料都非常方便,時間複雜度為o(1),這也是lpush,lpop,llen等命令效率較高的原因。
[cpp]view plain
copy
/** 建立乙個新鍊錶
** 建立成功時返回鍊錶,建立失敗返回 null**/
list *listcreate(void
)
建立鍊錶即是分配struct list的大小的記憶體,並初始化鍊錶節點數目為
0、鍊錶頭尾指標和基本函式指標為
null
。[cpp]view plain
copy
/** 釋放整個鍊錶(以及列表包含的節點)
** t = o(n),n 為鍊錶的長度
*/void
listrelease(list *list)
zfree(list); //釋放鍊錶本身的管理結構的記憶體
}
釋放列表即從表頭根據鍊錶節點數目遍歷,並依次釋放各個節點。如果鍊錶定義了free
方法,則先釋放節點值占用的記憶體。最後,釋放鍊錶本身的管理結構的記憶體。
[cpp]view plain
copy
/* 新建乙個包含給定 value 的節點,並將它加入到鍊錶的表頭
** 出錯時,返回 null ,不執行動作。
* 成功時,返回傳入的鍊錶
** t = o(1)
*/list *listaddnodehead(list *list, void
*value)
else
list->len++;
return
list;
}
新增節點的**很簡潔。首先分配記憶體空間,然後設定節點值為指定的值。然後建立鏈結關係:如果是第乙個節點,直接設定表頭和表尾指標為新加入節點;否則,設定新加入節點與原來的頭節點的鏈結關係,並設定頭節點為新加入的節點。最後更新鍊錶節點數目,並返回傳入的鍊錶。
[cpp]view plain
copy
/** 新建乙個包含給定 value 的節點,並將它加入到鍊錶的表尾
** 出錯時,返回 null ,不執行動作。
* 成功時,返回傳入的鍊錶
** t = o(1)
*/list *listaddnodetail(list *list, void
*value)
else
list->len++;
return
list;
}
與表頭新增節點類似,只是新增在了鍊錶尾部而已。
[cpp]view plain
copy
/** 建立乙個包含值 value 的節點
* 並根據 after 引數的指示,將新節點插入到 old_node 的之前或者之後
** t = o(1)
*/list *listinsertnode(list *list, listnode *old_node, void
*value,
intafter)
} else
}
// 更新前置節點和後繼節點的指標
if(node->prev != null)
if(node->next != null)
// 更新鍊錶節點數量
list->len++;
return
list;
}
這裡需要注意的是處理表頭和表尾節點,以及後面更新前置節點和後繼節點指標部分。
[cpp]view plain
copy
/** 刪除鍊錶中指定的節點
* 清除節點私有值(private value)的工作由呼叫者完成
** t = o(1)
*/void
listdelnode(list *list, listnode *node)
redis為雙向鍊錶還實現了乙個迭代器,可以從兩個方向迭代鍊錶。迭代器定義如下:
[cpp]view plain
copy
/** 鍊錶迭代器
*/typedef
struct
listiter listiter;
direction如果設定為al_start_head(0),則從表頭到表尾迭代。
direction如果設定為
al_start_tail(1)
,則從表尾向表頭迭代。
[cpp]view plain
copy
/** 這個函式不處理失敗情形
** t = o(1)
*/listiter *listgetiterator(list *list, int
direction)
[cpp]view plain
copy
/** 釋放迭代器 iter
** t = o(1)
*/void
listreleaseiterator(listiter *iter)
/** 將迭代器 iter 的迭代指標倒回 list 的表頭
** t = o(1)
*/void
listrewind(list *list, listiter *li)
/** 將迭代器 iter 的迭代指標倒回 list 的表尾
** t = o(1)
*/void
listrewindtail(list *list, listiter *li)
[cpp]view plain
copy
/** 返回迭代器的當前節點
** 可以使用 listdelnode() 刪除當前節點,但是不可以刪除其他節點。
** 函式要麼返回當前節點,要麼返回 null ,因此,常見的用法是:
* * iter = listgetiterator(list,);
* while ((node = listnext(iter)) != null)
** t = o(1)
*/listnode *listnext(listiter *iter)
return
current;
}
迭代器在迭代過程中,根據迭代方向,每次更新next指標即可。
Redis原始碼學習4 基本資料結構之字典
redis是乙個鍵值對資料庫,在很多地方用到字典。redis 字典的實現採用的是比較經典的雜湊表方式實現的。貌似跟 memcached 的方法有點像,很久之前看過部分 memcached 現在忘得差不多了。redis 的字典定義如下 cpp view plain copy 字典 每個字典使用兩個雜湊...
Redis原始碼學習4 基本資料結構之字典
redis基本資料結構 字典 字典概念 相關函式 建立字典 新增鍵值對到字典 獲取元素值 其他參考資料 redis是乙個鍵值對資料庫,在很多地方用到字典。redis 字典的實現採用的是比較經典的雜湊表方式實現的。貌似跟 memcached 的方法有點像,很久之前看過部分 memcached 現在忘得...
Python原始碼學習筆記(1 基本資料型別)
python原始碼剖析 這本書相當好。我用python也有幾個月時間了,這時候讀python原始碼,會對提高c語言水平 python水平 演算法基礎都有相當的幫助。python原始碼剖析.chm 這個檔案。學習心得嘛,就是多看多想,有問題的時候再除錯python原始碼驗證想法。robert chen...