目錄
redis源碼分析系列文章
前言
hello,又見面了。不要問爲什麼,問就是勤勞。馬上要開啓爆更模式啦。在Redis中鏈表List的應用非常廣泛,但是Redis是採用C語言來寫,底層採用雙向鏈表
實現(這邊提一嘴,如果是科班出身或者大學有學過數據結構的同學,可以划走啦)。我們今天的重點就是雙向鏈表。
API使用
先來使用一下API。如果之前有用過的同學,可以直接跳到下一小節。
lpush左側插入數據
使用lpush命令往list的左側中插入a,b,c三個字符,這邊注意順序,查詢出來的是c,b,a。下面會說爲什麼,先挖個坑。
rpush右側插入數據
使用rpush命令往list中插入d,e兩個字符,查詢出來的順序是和我們想的一樣,最後兩位是d,e。
刪除某個數據
使用lrem命令刪除a字符,那麼中間1代表什麼意思呢?其爲count,表示移除列表中與a相等的元素個數。即如果count>0,表示從表頭開始向表尾搜索,移除count個與a相等的元素。如果count<0,表示從表尾開始向表頭搜索,移除count個與a相等的元素。如果count=0,移除所有與a相等的元素,因爲是移除所有,所以不管從表頭還是表尾,結果是一樣的。
修改某個數據
使用lset命令將mylist的下標爲1的元素修改爲dd,原來list爲c ,b,d,e,修改後的結果爲c,dd,d,e。
具體邏輯圖
這邊看不懂沒關係,下面會針對每個模塊詳細說明。
雙向鏈表的定義
節點ListNode
包括頭指針prev,尾指針next,當前的值value,如下圖所示。每個節點都有兩個指針,既能從表頭根據尾指針找到表尾,又能從表尾根據頭指針prev找到表頭,如果將他們連起來,就構成了雙向鏈表。
具體代碼如下:
整體架構
包括頭指針head,尾指針tail,整個鏈表長度len,一些函數(個人認爲不重要,如果有知道的小夥伴歡迎評論),如下圖所示。頭指針head指向整個鏈表的第一個節點,尾指針tail指向整個鏈表的最後一個節點。
具體代碼如下:
雙向鏈表的實現
創建表頭
我們創建list表結構,首先需要判斷當前是否有可分配的空間來創建,使用zmalloc方法來分配空間,如果分配不了,則返回NULL,如果可以分配,則繼續。接着賦值list的頭節點head和尾節點tail爲NULL,len爲0,賦值相關函數爲NULL。最後返回結果list。
清空表
傳入list的指針,首先定義當前節點current,使其指向頭指針,定義len,使其等於list的長度。接着進行循環,每次len減一,定義新節點next,始終指向當前節點current的下一個節點,如果有值,則釋放該節點,當前節點current後移,next節點同樣後移。直到len爲0,釋放完所有節點,退出循環。最後賦值list的頭節點head和尾節點tail爲NULL,len爲0。
注意:這邊和SDS一樣,清空並不是直接刪除list,而是刪除其數據,外層的list結構仍然存在。這其實上是惰性刪除。
添加元素到表頭
添加元素到表頭,首先新建一個新節點node,判斷是否有內存分配,如果有,則繼續,如果沒有,則返回NULL,退出方法。這邊新節點是用來存在輸入參數中的value的,所以需要內存。接着將新節點node的value值賦值爲輸入參數value。最後需要調整list的頭指針,尾指針,原來第一個節點的指針情況(這邊看下圖,描述起來有點混亂,圖片一目瞭然)。最最後,就是list的len加1,返回list。
舉個例子,如果要在list中插入節點f,首先將節點的頭指針賦值爲空(對應步驟1),然後將新節點的尾指針next指向第一個節點(對應步驟2),將第一個節點的prev指向新節點(對應步驟3),最後將list的頭指針head指向新節點(對應步驟4)。這邊需要注意的是,步驟2和步驟3需要在步驟4前面,不然會找到第一個節點。
具體代碼如下:
添加元素到表尾
添加元素到表尾,首先新建一個新節點node,判斷是否有內存分配,如果有,則繼續,如果沒有,則返回NULL,退出方法。這邊新節點是用來存在輸入參數中的value的,所以需要內存。接着將新節點node的value值賦值爲輸入參數value。最後需要調整list的頭指針,尾指針,原來最後一個節點的指針情況(這邊看下圖,描述起來有點混亂,圖片一目瞭然)。最最後,就是list的len加1,返回list。
舉個例子,如果要在list中插入節點f,首先將節點的尾指針賦值爲空(對應步驟1),然後將新節點的頭指針指向最後一個節點(對應步驟2),將最後一個節點的next指向新節點(對應步驟3),最後將list的尾指針tail指向新節點(對應步驟4)。
步驟如下:
插入
爲list的某個節點old_node的after(前後)查詢新值value,首先新建一個新節點node,判斷是否有內存分配,如果有,則繼續,如果沒有,則返回NULL,退出方法。這邊新節點是用來存在輸入參數中的value的,所以需要內存。(這段話是不是聽的耳朵都起繭子啦🤣)接着根據after的值確定是在節點old_node的前面插入新數據,還是在節點old_node的後面插入新數據,具體的是指針的調整。最後len加1,返回list。
刪除
從list中刪除節點node,如果該節點的前面存在節點,使其前面一個節點的next指針指向node後面一個節點的地址,其實就是跳過了node節點,如果該節點的前面不存在節點,則將list的頭指針指向node的下一節點地址。同樣的,如果該節點的後面存在節點,邏輯一樣的。最後釋放要刪除的節點node內存,len減1。
總結
該篇主要講了Redis的list數據類型的底層實現雙向鏈表adlist,先從list的一些API使用,引出雙向鏈表數據結構,進而結合源碼對雙向鏈表進行描述,包括節點listNode和list的頭指針和尾指針,最後針對list的往表頭插入元素,往表尾插入元素,刪除,修改等方法進行源碼解析,使其對雙向鏈表有更清晰的認識。
如果覺得寫得還行,麻煩給個贊👍,您的認可纔是我寫作的動力!
如果覺得有說的不對的地方,歡迎評論指出。
好了,拜拜咯。