一、前言
Redis 提供了5種數據類型:String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Zset(有序集合),理解每種數據類型的特點對於redis的開發和運維非常重要。
Redis 中的 list 是我們經常使用到的一種數據類型,根據使用方式的不同,可以應用到很多場景中。
二、操作命令
List數據類型在 Redis 中的相關命令::
命令 | 描述 | 用法 |
---|---|---|
LPUSH | 1.將一個或多個值value插入到列表key的表頭 2.如果有多個value值,那么各個value值按從左到右的順序依次插入表頭 3.key不存在,一個空列表會被創建並執行LPUSH操作 4.key存在但不是列表類型,返回錯誤 |
LPUSH key value [value ...] |
LPUSHX | 1.將值value插入到列表key的表頭,當且僅當key存在且為一個列表 2.key不存在時,LPUSHX命令什么都不做 |
LPUSHX key value |
LPOP | 1.移除並返回列表key的頭元素 | LPOP key |
LRANGE | 1.返回列表key中指定區間內的元素,區間以偏移量start和stop指定 2.start和stop都以0位底 3.可使用負數下標,-1表示列表最后一個元素,-2表示列表倒數第二個元素,以此類推 4.start大於列表最大下標,返回空列表 5.stop大於列表最大下標,stop=列表最大下標 |
LRANGE key start stop |
LREM | 1.根據count的值,移除列表中與value相等的元素 2.count>0表示從頭到尾搜索,移除與value相等的元素,數量為count 3.count<0表示從從尾到頭搜索,移除與value相等的元素,數量為count 4.count=0表示移除表中所有與value相等的元素 |
LREM key count value |
LSET | 1.將列表key下標為index的元素值設為value 2.index參數超出范圍,或對一個空列表進行LSET時,返回錯誤 |
LSET key index value |
LINDEX | 1.返回列表key中,下標為index的元素 | LINDEX key index |
LINSERT | 1.將值value插入列表key中,位於pivot前面或者后面 2.pivot不存在於列表key時,不執行任何操作 3.key不存在,不執行任何操作 |
LINSERT key BEFORE |
LLEN | 1.返回列表key的長度 2.key不存在,返回0 |
LLEN key |
LTRIM | 1.對一個列表進行修剪,讓列表只返回指定區間內的元素,不存在指定區間內的都將被移除 | LTRIM key start stop |
RPOP | 1.移除並返回列表key的尾元素 | RPOP key |
RPOPLPUSH | 在一個原子時間內,執行兩個動作: 1.將列表source中最后一個元素彈出並返回給客戶端 2.將source彈出的元素插入到列表desination,作為destination列表的頭元素 |
RPOPLPUSH source destination |
RPUSH | 1.將一個或多個值value插入到列表key的表尾 | RPUSH key value [value ...] |
RPUSHX | 1.將value插入到列表key的表尾,當且僅當key存在並且是一個列表 2.key不存在,RPUSHX什么都不做 |
RPUSHX key value |
實踐:別偷懶,動手一下,try it out

三、應用場景
1、lpush+lpop=Stack(棧)
2、lpush+rpop=Queue(隊列)
3、lpush+ltrim=Capped Collection(有限集合)
4、lpush+brpop=Message Queue(消息隊列)
5、排行榜,數據最新列表等等![]()
四、底層解析
結構圖上顯示,List類型有兩種實現方式:
舉例說明,創建列表對象 numbers

1、使用壓縮列表(ziplist)實現的列表對象
結構如下

2、使用雙端鏈表(linkedlist)實現的列表對象
結構如下

五、疑問思考
壓縮列表與雙端鏈表是什么樣的結構?
1、壓縮列表(ziplist)
壓縮列表(ziplist)是Redis為了節省內存而開發的,是由一系列特殊編碼的連續內存塊組成的順序型數據結構,一個壓縮列表可以包含任意多個節點(entry),每個節點可以保存一個字節數組或者一個整數值。
結構如下

壓縮列表的每個節點構成如下

1)previous_entry_ength:以字節為單位,記錄了壓縮列表中前一個字節的長度。previous_entry_ength 的長度可以是1字節或者5字節:
如果前一節點的長度小於254字節,那么 previous_entry_ength 屬性的長度為1字節,前一節點的長度就保存在這一個字節里面。
如果前一個節點的長度大於等於254,那么 previous_entry_ength 屬性的長度為5字節,其中屬性的第一字節會被設置為0xFE(十進制254),而之后的四個字節則用於保存前一節點的長度。
利用此原理即當前節點位置減去上一個節點的長度即得到上一個節點的起始位置,壓縮列表可以從尾部向頭部遍歷,這么做很有效地減少了內存的浪費。
2)encoding:記錄了節點的 content 屬性所保存數據的類型以及長度。
3)content:保存節點的值,節點的值可以是一個字節數組或者整數,值的類型和長度由節點的 encoding 屬性決定。
2、雙端鏈表(linkedlist)
鏈表是一種常用的數據結構,C 語言內部是沒有內置這種數據結構的實現,所以Redis自己構建了鏈表的實現。
鏈表節點定義:
typedef struct listNode{
//前置節點
struct listNode *prev;
//后置節點
struct listNode *next;
//節點的值
void *value;
}listNode
多個 listNode 可以通過 prev 和 next 指針組成雙端鏈表,結構如下

另外Redis還提供了操作鏈表的數據結構:
typedef struct list{
//表頭節點
listNode *head;
//表尾節點
listNode *tail;
//鏈表所包含的節點數量
unsigned long len;
//節點值復制函數
void (*free) (void *ptr);
//節點值釋放函數
void (*free) (void *ptr);
//節點值對比函數
int (*match) (void *ptr,void *key);
}list;
list結構為鏈表提供了表頭指針 head ,表尾指針 tail 以及鏈表長度計數器 len ,dup、free、match 成員則是用於實現多態鏈表所需的類型特定函數。
Redis鏈表實現的特性:
- 雙端:鏈表節點帶有 prev 和 next 指針,獲取某個節點的前置節點和后置節點復雜度都是O(1)。
- 無環:表頭節點的 prev 指針和表尾節點的 next 指針都指向 NULL,對鏈表的訪問以NULL為終點。
- 帶表頭指針和表尾指針:通過list結構的 head 和 tail 指針,程序獲取鏈表的表頭節點和表尾結點的復雜度都是O(1)。
- 帶鏈表長度計數器:程序使用 list 結構的 len屬性對 list持有的鏈表節點進行計數,程序獲取鏈表中節點數量的復雜度為O(1)。
- 多態:鏈表節點使用 void* 指針來保存節點值,並且通過 list 結構的 dup、 free、match 三個屬性為節點值設置類型特定函數,所以鏈表可以用於保存各種不同類型的值。
疑問:Redis列表什么時候會使用 ziplist 編碼,什么時候又會使用 linkedlist 編碼呢?下節再說...
