• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            posts - 200, comments - 8, trackbacks - 0, articles - 0

             轉(zhuǎn)自:http://www.redisbook.com

            雙端鏈表

            鏈表作為數(shù)組之外的一種常用序列抽象, 是大多數(shù)高級語言的基本數(shù)據(jù)類型, 因為 C 語言本身不支持鏈表類型, 大部分 C 程序都會自己實現(xiàn)一種鏈表類型, Redis 也不例外 —— 它實現(xiàn)了一個雙端鏈表結(jié)構(gòu)。

            雙端鏈表作為一種常見的數(shù)據(jù)結(jié)構(gòu), 在大部分的數(shù)據(jù)結(jié)構(gòu)或者算法書里都有講解, 因此, 這一章關(guān)注的是 Redis 雙端鏈表的具體實現(xiàn), 以及該實現(xiàn)的 API , 而對于雙端鏈表本身, 以及雙端鏈表所對應(yīng)的算法, 則不做任何解釋。

            讀者如果有需要的話,可以參考維基百科的雙端鏈表詞條,里面提供了關(guān)于雙端鏈表的一些基本信息。

            另外,一些書籍,比如《算法:C 語言實現(xiàn)》《數(shù)據(jù)結(jié)構(gòu)與算法分析》則提供了關(guān)于雙端鏈表的更詳細(xì)的信息。

            雙端鏈表的應(yīng)用

            雙端鏈表作為一種通用的數(shù)據(jù)結(jié)構(gòu), 在 Redis 內(nèi)部使用得非常多: 它既是 Redis 列表結(jié)構(gòu)的底層實現(xiàn)之一, 還被大量 Redis 模塊所使用, 用于構(gòu)建 Redis 的其他功能。

            實現(xiàn) Redis 的列表類型

            雙端鏈表還是 Redis 列表類型的底層實現(xiàn)之一, 當(dāng)對列表類型的鍵進(jìn)行操作 —— 比如執(zhí)行 RPUSH 、 LPOP 或 LLEN 等命令時, 程序在底層操作的可能就是雙端鏈表。

            redis> RPUSH brands Apple Microsoft Google
            (integer) 
            3

            redis
            > LPOP brands
            "Apple"

            redis
            > LLEN brands
            (integer) 
            2

            redis
            > LRANGE brands 0 -1
            1"Microsoft"
            2"Google"

            Redis 列表使用兩種數(shù)據(jù)結(jié)構(gòu)作為底層實現(xiàn):

            1. 雙端鏈表
            2. 壓縮列表

            因為雙端鏈表占用的內(nèi)存比壓縮列表要多, 所以當(dāng)創(chuàng)建新的列表鍵時, 列表會優(yōu)先考慮使用壓縮列表作為底層實現(xiàn), 并且在有需要的時候, 才從壓縮列表實現(xiàn)轉(zhuǎn)換到雙端鏈表實現(xiàn)。

            后續(xù)章節(jié)會對壓縮鏈表和 Redis 類型做更進(jìn)一步的介紹。

            Redis 自身功能的構(gòu)建

            除了實現(xiàn)列表類型以外, 雙端鏈表還被很多 Redis 內(nèi)部模塊所應(yīng)用:

            • 事務(wù)模塊使用雙端鏈表來按順序保存輸入的命令;
            • 服務(wù)器模塊使用雙端鏈表來保存多個客戶端;
            • 訂閱/發(fā)送模塊使用雙端鏈表來保存訂閱模式的多個客戶端;
            • 事件模塊使用雙端鏈表來保存時間事件(time event);

            類似的應(yīng)用還有很多, 在后續(xù)的章節(jié)中我們將看到, 雙端鏈表在 Redis 中發(fā)揮著重要的作用。

            雙端鏈表的實現(xiàn)

            雙端鏈表的實現(xiàn)由 listNode 和 list 兩個數(shù)據(jù)結(jié)構(gòu)構(gòu)成, 下圖展示了由這兩個結(jié)構(gòu)組成的一個雙端鏈表實例:


            其中, listNode 是雙端鏈表的節(jié)點:
            typedef struct listNode {

                
            // 前驅(qū)節(jié)點
                struct listNode *prev;

                
            // 后繼節(jié)點
                struct listNode *next;

                
            // 值
                void *value;

            } listNode;
            而 list 則是雙端鏈表本身:
            typedef struct list {

                
            // 表頭指針
                listNode *head;

                
            // 表尾指針
                listNode *tail;

                
            // 節(jié)點數(shù)量
                unsigned long len;

                
            // 復(fù)制函數(shù)
                void *(*dup)(void *ptr);
                
            // 釋放函數(shù)
                void (*free)(void *ptr);
                
            // 比對函數(shù)
                int (*match)(void *ptr, void *key);
            } list;

            注意, listNode 的 value 屬性的類型是 void * ,說明這個雙端鏈表對節(jié)點所保存的值的類型不做限制。

            對于不同類型的值,有時候需要不同的函數(shù)來處理這些值,因此, list 類型保留了三個函數(shù)指針 —— dup 、 free 和 match ,分別用于處理值的復(fù)制、釋放和對比匹配。在對節(jié)點的值進(jìn)行處理時,如果有給定這些函數(shù),那么它們就會被調(diào)用。

            舉個例子:當(dāng)刪除一個 listNode 時,如果包含這個節(jié)點的 list 的 list->free 函數(shù)不為空,那么刪除函數(shù)就會先調(diào)用 list->free(listNode->value) 清空節(jié)點的值,再執(zhí)行余下的刪除操作(比如說,釋放節(jié)點)。

            另外,從這兩個數(shù)據(jù)結(jié)構(gòu)的定義上,也可以它們的一些行為和性能特征:

            • listNode 帶有 prev 和 next 兩個指針,因此,對鏈表的遍歷可以在兩個方向上進(jìn)行:從表頭到表尾,或者從表尾到表頭。
            • list 保存了 head 和 tail 兩個指針,因此,對鏈表的表頭和表尾進(jìn)行插入的復(fù)雜度都為 θ(1) —— 這是高效實現(xiàn) LPUSH 、 RPOP 、RPOPLPUSH 等命令的關(guān)鍵。
            • list 帶有保存節(jié)點數(shù)量的 len 屬性,所以計算鏈表長度的復(fù)雜度僅為 θ(1) ,這也保證了 LLEN 命令不會成為性能瓶頸。

            以下是用于操作雙端鏈表的 API ,它們的作用以及算法復(fù)雜度:

            函數(shù)作用算法復(fù)雜度
            listCreate創(chuàng)建一個新鏈表O(1)
            listRelease釋放一個鏈表,以及該鏈表所包含的節(jié)點O(N)
            listDup創(chuàng)建一個給定鏈表的副本O(N)
            listRotate取出鏈表的表尾節(jié)點,將它插入到表頭O(1)
            listAddNodeHead將一個包含給定值的節(jié)點添加到鏈表的表頭O(1)
            listAddNodeTail將一個包含給定值的節(jié)點添加到鏈表的表尾O(1)
            listInsertNode將一個包含給定值的節(jié)點添加到某個節(jié)點的之前或之后O(1)
            listDelNode刪除給定節(jié)點O(1)
            listSearchKey在鏈表中查找和給定 key 匹配的節(jié)點O(N)
            listIndex給據(jù)給定索引,返回列表中相應(yīng)的節(jié)點O(N)
            listLength返回給定鏈表的節(jié)點數(shù)量O(1)
            listFirst返回鏈表的表頭節(jié)點O(1)
            listLast返回鏈表的表尾節(jié)點O(1)
            listPrevNode返回給定節(jié)點的前一個節(jié)點O(1)
            listNextNode返回給定節(jié)點的后一個節(jié)點O(1)
            listNodeValue返回給定節(jié)點的值O(1)

            迭代器

            Redis 為雙端鏈表實現(xiàn)了一個迭代器 , 這個迭代器可以從兩個方向?qū)﹄p端鏈表進(jìn)行迭代:

            • 沿著節(jié)點的 next 指針前進(jìn),從表頭向表尾迭代;
            • 沿著節(jié)點的 prev 指針前進(jìn),從表尾向表頭迭代;

            以下是迭代器的數(shù)據(jù)結(jié)構(gòu)定義:

            typedef struct listIter {

                
            // 下一節(jié)點
                listNode *next;

                
            // 迭代方向
                int direction;

            } listIter;

            direction 記錄迭代應(yīng)該從那里開始:

            • 如果值為 adlist.h/AL_START_HEAD ,那么迭代器執(zhí)行從表頭到表尾的迭代;
            • 如果值為 adlist.h/AL_START_TAIL ,那么迭代器執(zhí)行從表尾到表頭的迭代;

            以下是迭代器的操作 API ,它們的作用以及算法復(fù)雜度:

            函數(shù)作用算法復(fù)雜度
            listGetIterator創(chuàng)建一個列表迭代器O(1)
            listReleaseIterator釋放迭代器O(1)
            listRewind將迭代器的指針指向表頭O(1)
            listRewindTail將迭代器的指針指向表尾O(1)
            listNext取出迭代器當(dāng)前指向的節(jié)點O(1)

            小結(jié)

            • Redis 實現(xiàn)了自己的雙端鏈表結(jié)構(gòu)。
            • 雙端鏈表主要有兩個作用:
              • 作為 Redis 列表類型的底層實現(xiàn)之一;
              • 作為通用數(shù)據(jù)結(jié)構(gòu),被其他功能模塊所使用;
            • 雙端鏈表及其節(jié)點的性能特性如下:
              • 節(jié)點帶有前驅(qū)和后繼指針,訪問前驅(qū)節(jié)點和后繼節(jié)點的復(fù)雜度為 O(1) ,并且對鏈表的迭代可以在從表頭到表尾和從表尾到表頭兩個方向進(jìn)行;
              • 鏈表帶有指向表頭和表尾的指針,因此對表頭和表尾進(jìn)行處理的復(fù)雜度為 O(1) ;
              • 鏈表帶有記錄節(jié)點數(shù)量的屬性,所以可以在 O(1) 復(fù)雜度內(nèi)返回鏈表的節(jié)點數(shù)量(長度);








             

            久久九九全国免费| 久久人做人爽一区二区三区| 久久免费精品视频| 久久无码人妻精品一区二区三区| 热99RE久久精品这里都是精品免费 | 国内精品久久久久影院优| 9191精品国产免费久久 | 久久精品国产精品国产精品污| 久久免费99精品国产自在现线| 久久综合亚洲欧美成人| 人妻无码精品久久亚瑟影视| 99久久婷婷免费国产综合精品| 欧美亚洲国产精品久久久久| 国产精品美女久久久久网| 国产精品中文久久久久久久 | 久久精品国产亚洲AV蜜臀色欲| 青青热久久综合网伊人| 人妻无码久久一区二区三区免费| 亚洲国产香蕉人人爽成AV片久久| 青青青伊人色综合久久| 91精品国产乱码久久久久久| 伊人久久大香线蕉av一区| 中文字幕无码久久精品青草| 久久青青草原亚洲av无码| 成人精品一区二区久久久| 久久A级毛片免费观看| 亚洲熟妇无码另类久久久| 少妇熟女久久综合网色欲| 人妻无码精品久久亚瑟影视| 亚洲а∨天堂久久精品| 日本久久中文字幕| 青青草国产97免久久费观看| 久久精品国产福利国产琪琪| 久久国产综合精品五月天| 亚洲国产精品久久久久婷婷老年| 精品综合久久久久久888蜜芽| 亚洲AV无码成人网站久久精品大| 久久精品成人欧美大片| 亚洲av成人无码久久精品| 久久人人爽爽爽人久久久| 久久久婷婷五月亚洲97号色|