深度解讀|Redis 避不開的五種數據結構

深度解讀|Redis 避不開的五種數據結構

關注開源中國OSC頭條號,獲取最新技術資訊


Redis 中有 5 種數據結構,分別是字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set),因為使用 Redis 場景的開發中肯定是無法避開這些基礎結構的,所以熟練掌握它們也就成了一項必不可少的能力。本文章精要地介紹了 Redis 的這幾種數據結構,主要覆蓋了它們各自的定義、基本用法與相關要點。

字符串類型

字符串是 Redis 中的最基礎的數據結構,我們保存到 Redis 中的 key,也就是鍵,就是字符串結構的。除此之外,Redis 中其它數據結構也是在字符串的基礎上設計的,可見字符串結構對於 Redis 是多麼重要。

Redis 中的字符串結構可以保存多種數據類型,如:簡單的字符串、JSON、XML、二進制等,但有一點要特別注意:在 Redis 中字符串類型的值最大隻能保存 512 MB。

深度解讀|Redis 避不開的五種數據結構

命令

下面通過命令瞭解一下對字符串類型的操作:

1.設置值

set key value [EX seconds] [PX milliseconds] [NX|XX]
深度解讀|Redis 避不開的五種數據結構

set 命令有幾個非必須的選項,下面我們看一下它們的具體說明:

  • EX seconds:為鍵設置秒級過期時間
  • PX milliseconds:為鍵設置毫秒級過期時間
  • NX:鍵必須不存在,才可以設置成功,用於添加
  • XX:鍵必須存在,才可以設置成功,用於更新
深度解讀|Redis 避不開的五種數據結構

set 命令帶上可選參數 NX 和 XX 在實際開發中的作用與 setnx 和 setxx 命令相同。我們知道 setnx 命令只有當 key 不存在的時候才能設置成功,換句話說,也就是同一個 key 在執行 setnx 命令時,只能成功一次,並且由於 Redis 的單線程命令處理機制,即使多個客戶端同時執行 setnx 命令,也只有一個客戶端執行成功。所以,基於 setnx 這種特性,

setnx 命令可以作為分佈式鎖的一種解決方案

而 setxx 命令則可以在安全性比較高的場景中使用,因為 set 命令執行時,會執行覆蓋的操作,而 setxx 在更新 key 時可以確保該 key 已經存在了,所以為了保證 key 中數據類型的正確性,可以使用 setxx 命令。

2.獲取值

get key
深度解讀|Redis 避不開的五種數據結構

3.批量設置值

mset key value
深度解讀|Redis 避不開的五種數據結構

4.批量獲取值

mget key
深度解讀|Redis 避不開的五種數據結構

如果有些鍵不存在,那麼它的值將為 nil,也就是空,並且返回結果的順序與傳入時相同。

深度解讀|Redis 避不開的五種數據結構

5.計數

incr key

incr 命令用於對值做自增操作,返回的結果分為 3 種情況:

  • 如果值不是整數,那麼返回的一定是錯誤
  • 如果值是整數,那麼返回自增後的結果
  • 如果鍵不存在,那麼就會創建此鍵,然後按照值為 0 自增, 就是返回 1
深度解讀|Redis 避不開的五種數據結構


除了有 incr 自增命令外,Redis 中還提供了其它對數字處理的命令。例如:

decr key 自減
incrby kek increment 自增指定數字
decrby key decrement 自減指定數字
incrbyfloat key increment 自增浮點數
深度解讀|Redis 避不開的五種數據結構

6.追加值

append key value

append 命令可以向字符串尾部追加值。

深度解讀|Redis 避不開的五種數據結構

7.字符串長度

strlen key
深度解讀|Redis 避不開的五種數據結構

由於每個中文佔用 3 個字節,所以 jilinwula 這個鍵,返回是字符串長度為 12,而不是 4。

8.設置並返回原值

getset key value
深度解讀|Redis 避不開的五種數據結構

9.設置指定位置的字符

setrange key offeset value
深度解讀|Redis 避不開的五種數據結構

10.獲取部分字符串

getrange key start end
深度解讀|Redis 避不開的五種數據結構

時間複雜度

在 Redis 中執行任何命令時,都有相應的時間複雜度,複雜度越高也就越費時間,所以在執行 Redis 中的命令時,如果要執行的命令複雜度越高,就越要慎重。下面是字符串命令時間複雜度類型表:

深度解讀|Redis 避不開的五種數據結構

內部編碼

在 Redis 中字符串類型的內部編碼有 3 種:

  • int:8 個字節的長整型
  • embstr:小於等於 39 個字節的字符串
  • raw:大於 39 個字節的字符串
深度解讀|Redis 避不開的五種數據結構

哈希類型

大部分語言基本都提供了哈希類型,如 Java 語言中的 Map 類型及 Python 語言中的字典類型等等。雖然語言不同,但它們基本使用都是一樣的,也就是都是鍵值對結構的。例如:

value={{field1, value1}

通過下圖可以直觀感受一下字符串類型和哈希類型的區別:

深度解讀|Redis 避不開的五種數據結構

Redis 中哈希類型都是鍵值對結構的,所以要特別注意這裡的 value 並不是指 Redis 中 key 的 value,而是哈希類型中的 field 所對應的 value。

命令

下面我們還是和介紹字符串類型一樣,瞭解一下 Redis 中哈希類型的相關命令。

1.設置值

hset key field value
深度解讀|Redis 避不開的五種數據結構

我們看上圖執行的命令知道,hset 命令也是有返回值的。如果 hset 命令設置成功,則返回 1,否則返回 0。除此之外 Redis 也為哈希類型提供了 hsetnx 命令。在前文對字符串的介紹中,我們知道 nx 命令只有當 key 不存在的時候,才能設置成功,同樣的,hsetnx 命令在 field 不存在的時候,才能設置成功。

2.獲取值

hget key field
深度解讀|Redis 避不開的五種數據結構

我們看 hget 命令和 get 有很大的不同,get 命令在獲取的時候,只要寫一個名字就可以了,而 hget 命令則要寫兩個名字,第一個名字是 key,第二個名字是 field。當然 key 或者 field 不存在時,返回的結果都是 nil。

3.刪除 field

hdel key field [field ...]
深度解讀|Redis 避不開的五種數據結構

hdel 命令刪除的時候,也會有返回值,並且這個返回就是成功刪除 field 的個數。當 field 不存在時,並不會報錯,而是直接返回 0。

4.計算 field 個數

hlen key
深度解讀|Redis 避不開的五種數據結構

hlen 命令返回的就是當前 key 中 field 的個數,如果 key 不存在,則返回 0。

5.批量設置或獲取 field-value

hmget key field [field ...]
hmset key field value [field value ...]
深度解讀|Redis 避不開的五種數據結構

hmset 命令和 hmget 命令分別是批量設置和獲取值的,hmset 命令沒有什麼要注意的,但 hmget 命令要特別注意,當我們獲取一個不存在的 key 或者不存在的 field 時,Redis 並不會報錯,而是返回 nil。並且有幾個 field 不存在,則 Redis 返回幾個 nil。

6.判斷 field 是否存在

hexists key field
深度解讀|Redis 避不開的五種數據結構

當執行 hexists 命令時,如果當前 key 包括 field,則返回 1,否則返回 0。

7.獲取所有 field

hkeys key
深度解讀|Redis 避不開的五種數據結構

8.獲取所有 value

hvals key
深度解讀|Redis 避不開的五種數據結構

9.獲取所有的 field-value

hgetall key
深度解讀|Redis 避不開的五種數據結構

hgetall 命令會返回當前 key 中的所有 field-value,並按照順序依次返回。

10.計數

hincrby key field increment
hincrbyfloat key field increment
深度解讀|Redis 避不開的五種數據結構

hincrby 命令和 incrby 命令的使用功能基本一樣,都是對值進行增量操作的,唯一不同的就是 incrby 命令的作用域是 key,而 hincrby 命令的作用域則是 field。

11.計算 value 的字符串長度

hstrlen key field
深度解讀|Redis 避不開的五種數據結構

hstrlen 命令返回的是當前 key 中 field 中字符串的長度,如果當前 key 中沒有 field 則返回 0。

時間複雜度

深度解讀|Redis 避不開的五種數據結構

內部編碼

Redis 哈希類型的內部編碼有兩種,它們分別是:

  • ziplist(壓縮列表):當哈希類型中元素個數小於 hash-max-ziplist-entries 配置(默認 512 個),同時所有值都小於 hash-max-ziplist-value 配置(默認 64 字節)時,Redis 會使用 ziplist 作為哈希的內部實現。
  • hashtable(哈希表):當上述條件不滿足時,Redis 則會採用 hashtable 作為哈希的內部實現。

下面我們通過以下命令來演示一下 ziplist 和 hashtable 這兩種內部編碼。

當 field 個數比較少並且 value 也不是很大時候 Redis 哈希類型的內部編碼為 ziplist:

深度解讀|Redis 避不開的五種數據結構

當 value 中的字節數大於 64 字節時(可以通過 hash-max-ziplist-value 設置),內部編碼會由 ziplist 變成 hashtable。

深度解讀|Redis 避不開的五種數據結構

當 field 個數超過 512(可以通過 hash-max-ziplist-entries 參數設置),內部編碼也會由 ziplist 變成 hashtable。

由於直接手動創建 512 個 field 不方便,為了更好的驗證該功能,我將用程序的方式,動態創建 512 個 field 來驗證此功能,下面為具體的代碼:

import redis 

r = redis.Redis(host='127.0.0.1', port=6379)
print('Key為【userinfo】的字節編碼為【%s】' % r.object('encoding', 'userinfo').decode('utf-8'))
for i in range(1,513):
r.hset('userinfo', i, '吉林烏拉')
print('Key為【userinfo】的字節編碼為【%s】' % r.object('encoding', 'userinfo').decode('utf-8'))
Key為【userinfo】的字節編碼為【ziplist】
Key為【userinfo】的字節編碼為【hashtable】

列表類型

Redis 中列表類型可以簡單地理解為存儲多個有序字符串的一種新類型,這種類型除了字符串類型中已有的功能外,還提供了其它功能,如可以對列表的兩端插入和彈出元素(在列表中的字符串都可以稱之為元素),除此之外還可以獲取指定的元素列表,並且還可以通過索引下標獲取指定元素等等。下面我們通過下圖來看一下 Redis 中列表類型的插入和彈出操作:

深度解讀|Redis 避不開的五種數據結構

下面我們看一下 Redis 中列表類型的獲取與刪除操作:

深度解讀|Redis 避不開的五種數據結構

Redis 列表類型的特點如下:

  • 列表中所有的元素都是有序的,所以它們是可以通過索引獲取的,也就是上圖中的 lindex 命令。並且在 Redis 中列表類型的索引是從 0 開始的。
  • 列表中的元素是可以重複的,也就是說在 Redis 列表類型中,可以保存同名元素,如下圖所示:
深度解讀|Redis 避不開的五種數據結構

命令

下面我們還是和學習其它數據類型一樣,我們還是先學習一下 Redis 列表類型的命令。

1.添加操作

  • 從右邊插入元素
rpush key value [value ...]
深度解讀|Redis 避不開的五種數據結構

我們看 rpush 命令在插入時,是有返回值的,返回值的數量就是當前列表中所有元素的個數。

我們也可以用下面的命令從左到右獲取當前列表中的所有的元素,也就是如上圖所示中那樣。

lrange 0 -1
  • 從左邊插入元素
lpush key value [value ...]
深度解讀|Redis 避不開的五種數據結構

lpush 命令的返回值及用法和 rpush 命令一樣。通過上面的事例證明了我們前面說的,rpush 命令和 lpush 命令的返回值並不是當前插入元素的個數,而是當前 key 中全部元素的個數,因為當前 key 中已經有了 3 個元素,所以我們在執行插入命令時,返回的就是 6 而不是 3,。

  • 向某個元素前或者後插入元素
linsert key BEFORE|AFTER pivot value
深度解讀|Redis 避不開的五種數據結構

linsert 命令在執行的時候首先會從當前列表中查找到 pivot 元素,其次再將這個新元素插入到 pivot 元素的前面或者後面。並且我們通過上圖可以知道 linsert 命令在執行成功後也是會有返回值的,返回的結果就是當前列表中元素的個數。

2.查找

  • 獲取指定範圍內的元素列表
lrange key start stop
深度解讀|Redis 避不開的五種數據結構

lrange 命令會獲取列表中指定索引範圍的所有元素。

通過索引獲取列表主要有兩個特點:

  • 索引下標從左到右分別是 0 到 N-1,從右到左是 -1 到 -N。
  • lrange 命令中的 stop 參數在執行時會包括當前元素,並不是所有的語言都是這樣的。我們要獲取列表中前兩個元素則可以如下圖所示:
深度解讀|Redis 避不開的五種數據結構

  • 獲取列表中指定索引下標的元素
lindex key index
深度解讀|Redis 避不開的五種數據結構

  • 獲取列表長度
llen key
深度解讀|Redis 避不開的五種數據結構

3.刪除

  • 從列表左側彈出元素
lpop key
深度解讀|Redis 避不開的五種數據結構

lpop 命令執行成功後會返回當前被刪除的元素名稱。

  • 從列表右側彈出元素
rpop key
深度解讀|Redis 避不開的五種數據結構

rpop 命令和 lpop 命令的使用方式一樣。

  • 刪除指定元素
lrem key count value

lrem 命令會將列表中等於 value 的元素刪除掉,並且會根據 count 參數來決定刪除 value 的元素個數。

下面我們看一下 count 參數的使用說明:

count > 0:表示從左到右,最多刪除 count 個元素。也就是如下圖所示:

深度解讀|Redis 避不開的五種數據結構

我們看上圖中的命令中,雖然我們將 count 參數指定的是 5,將 value 參數指定的是 2,但由於當前列表中只有一個 2,所以,當前 lrem 命令最多隻能刪除 1 個元素,並且 lrem 命令也是有返回值的,也就是當前成功刪除元素的個數。

深度解讀|Redis 避不開的五種數據結構

count < 0:從右到左,最多刪除 count 個元素。

深度解讀|Redis 避不開的五種數據結構

count = 0:刪除所有元素。

深度解讀|Redis 避不開的五種數據結構

  • 按照索引範圍修剪列表
ltrim key start stop

ltrim 命令會直接保留 start 索引到 stop 索引的之間的元素,幷包括當前元素,而之外的元素則都會刪除掉,所以該命令也叫修剪列表。

深度解讀|Redis 避不開的五種數據結構

並且有一點要注意,ltrim 命令不會返回當前的列表中元素的個數,而是返回改命令是否成功的狀態。

4.修改

  • 修改指定索引下標的元素
lset key index value
深度解讀|Redis 避不開的五種數據結構

5.阻塞操作

blpop key [key ...] timeout
brpop key [key ...] timeout

blpop 和 brpop 命令是 lpop 和 rpop 命令的阻塞版本,它們除了彈出方向不同以外,使用方法基本相同。

  • key [key ...]:可以指定多個列表的鍵。
  • timeout:阻塞時間(單位:秒)

下面我們看一下該命令的詳細使用。

列表為空:如果 timeout=3,則表示客戶端等待 3 秒後才能返回結果,如果 timeout=0,則表示客戶端會一直等待,也就是會阻塞。

深度解讀|Redis 避不開的五種數據結構

由於我期間向列表中插入了元素,否則上述命令將一直阻塞下去。

列表不為空:如果 timeout=0,並且列表不為空時,則 blpop 和 brpop 命令會立即返回結果,不會阻塞。

深度解讀|Redis 避不開的五種數據結構

下面我們看一下 blpop 和 brpop 命令的注意事項。

如果使用 blpop 和 brpop 命令指定多個鍵時,blpop 和 brpop 命令會從左到右遍歷鍵,並且一旦有一個鍵能返回元素,則客戶端會立即返回。

深度解讀|Redis 避不開的五種數據結構

當列表為空時,上述命令會阻塞,如果向上述中的任何一個鍵中插入元素,則上述命令會直接返回該鍵的元素。

如果多個客戶端都對同一個鍵執行 blpop 或者 brpop 命令,則最先執行該命令的客戶端會獲取到該鍵的元素。

深度解讀|Redis 避不開的五種數據結構

我同時啟動了 3 個客戶端,因為當前列表為空,所以上述命令執行後會阻塞。如果此時我向該列表中插入元素,則只有第一個客戶端會有返回結果,因為第一個客戶端是第一個執行上述命令的。

深度解讀|Redis 避不開的五種數據結構

深度解讀|Redis 避不開的五種數據結構

時間複雜度

下面我們看一下列表中命令的相關時間複雜度。

深度解讀|Redis 避不開的五種數據結構

內部編碼

列表中的內部編碼有兩種,它們分別是:

  • ziplist(壓縮列表):當列表中元素個數小於 512(默認)個,並且列表中每個元素的值都小於 64(默認)個字節時,Redis 會選擇用 ziplist 來作為列表的內部實現以減少內存的使用。當然上述默認值也可以通過相關參數修改:list-max-ziplist-entried(元素個數)、list-max-ziplist-value(元素值)。
  • linkedlist(鏈表):當列表類型無法滿足 ziplist 條件時,Redis 會選擇用 linkedlist 作為列表的內部實現。

集合類型

Redis 中的集合類型,也就是 set。在 Redis 中 set 也是可以保存多個字符串的,經常有人會分不清 list 與 set,下面我們重點介紹一下它們之間的不同:

  • set 中的元素是不可以重複的,而 list 是可以保存重複元素的。
  • set 中的元素是無序的,而 list 中的元素是有序的。
  • set 中的元素不能通過索引下標獲取元素,而 list 中的元素則可以通過索引下標獲取元素。
  • 除此之外 set 還支持更高級的功能,例如多個 set 取交集、並集、差集等。

命令

下面我們介紹一下 set 中的相關命令。

1.集合內操作

  • 添加元素
sadd key member [member ...]
深度解讀|Redis 避不開的五種數據結構

sadd 命令也是有返回值的,它的返回值就是當前執行 sadd 命令成功添加元素的個數,因為 set 中不能保存重複元素,所以在執行 sadd setkey c d 命令時,返回的是 1,而不是 2。因為元素 c 已經成功保存到 set 中,不能再保存了,只能將 d 保存到 set 中。

  • 刪除元素
srem key member [member ...]
深度解讀|Redis 避不開的五種數據結構

srem 命令和 sadd 命令一樣也是有返回值的,返回值就是當前刪除元素的個數。

  • 計算元素個數
scard key
深度解讀|Redis 避不開的五種數據結構

scard 命令的時間複雜度為O(1),scard 命令不會遍歷 set 中的所有元素,而是直接使用 Redis 中的內部變量。

  • 判讀元素是否在集合中
sismember key member
深度解讀|Redis 避不開的五種數據結構

sismember 命令也有返回值,如果返回值為1則表示當前元素在當前 set 中,如果返回 0 則表示當前元素不在 set 中。

  • 隨機從 set 中返回指定個數元素
srandmember key [count]
深度解讀|Redis 避不開的五種數據結構

srandmember 命令中有一個可選參數 count,count 參數指的是返回元素的個數,如果當前 set 中的元素個數小於 count,則 srandmember 命令返回當前 set 中的所有元素,如果 count 參數等於 0,則不返回任何數據,如果 count 參數小於 0,則隨機返回當前 count 個數的元素。

  • 從集合中隨機彈出元素
spop key [count]
深度解讀|Redis 避不開的五種數據結構

spop 命令也是隨機從 set 中彈出元素,並且也支持 count 可選參數,但有一點和 srandmember 命令不同。spop 命令在隨機彈出元素之後,會將彈出的元素從 set 中刪除。

  • 獲取所有元素
smembers key
深度解讀|Redis 避不開的五種數據結構

smembers 命令雖然能獲取當前 set 中所有的元素,但返回元素的順序與 sadd 添加元素的順序不一定相同,這也就是前面提到過的保存在 set 中的元素是無序的。

2.集合間操作

  • 集合的交集
sinter key [key ...]
深度解讀|Redis 避不開的五種數據結構

  • 集合的並集
sunion key [key ...]
深度解讀|Redis 避不開的五種數據結構

  • 集合的差集
sdiff key [key ...]
深度解讀|Redis 避不開的五種數據結構

  • 將集合的交集、並集、差集的結果保存
sinterstore destination key [key ...]
sunionstore destination key [key ...]
sdiffstore destination key [key ...]
深度解讀|Redis 避不開的五種數據結構

為什麼 Redis 要提供 sinterstore、sunionstore、sdiffstore 命令來將集合的交集、並集、差集的結果保存起來呢?這是因為 Redis 在進行上述比較時,會比較耗費時間,所以為了提高性能可以將交集、並集、差集的結果提前保存起來,這樣在需要使用時,可以直接通過 smembers 命令獲取。

時間複雜度

下面我們看一下 set 中相關命令的時間複雜度。

深度解讀|Redis 避不開的五種數據結構

內部編碼

  • intset(整數集合):當集合中的元素都是整數,並且集合中的元素個數小於 512 個時,Redis 會選用 intset 作為底層內部實現。
  • hashtable(哈希表):當上述條件不滿足時,Redis 會採用 hashtable 作為底層實現。

備註:我們可以通過 set-max-intset-entries 參數來設置上述中的默認參數。

下面我們看一下具體的事例,來驗證我們上面提到的內部編碼。

當元素個數較少並且都是整數時,內部編碼為 intset。

深度解讀|Redis 避不開的五種數據結構

當元素不全是整數時,內部編碼為 hashtable。

深度解讀|Redis 避不開的五種數據結構

當元素個數超過 512 個時,內部編碼為 hashtable。

import redis
r = redis.Redis(host='127.0.0.1', port=6379)
if r.object('encoding', 'setkey') != None:
print('Key為【setkey】的字節編碼為【%s】' % r.object('encoding', 'setkey').decode('utf-8'))
for i in range(1, 600):
r.sadd('setkey', i)
if r.object('encoding', 'setkey') != None:
print('Key為【setkey】的字節編碼為【%s】' % r.object('encoding', 'setkey').decode('utf-8'))
Key為【setkey】的字節編碼為【intset】
Key為【setkey】的字節編碼為【hashtable】

有序集合類型

看名字我們就知道,有序集合也是一種集合,並且這個集合還是有序的。列表也是有序的,那它和有序集合又有什麼不同呢?有序集合的有序和列表的有序是不同的。列表中的有序指的的是插入元素的順序和查詢元素的順序相同,而有序集合中的有序指的是它會為每個元素設置一個分數(score),而查詢時可以通過分數計算元素的排名,然後再返回結果。因為有序集合也是集合類型,所以有序集合中也是不插入重複元素的,但在有序集合中分數則是可以重複,那如果在有序集合中有多個元素的分數是相同的,這些重複元素的排名是怎麼計算的呢?後邊我們再做詳細說明。

下面先看一下列表、集合、有序集合三種數據類型之間的區別:

深度解讀|Redis 避不開的五種數據結構

命令

1.集合內操作

  • 添加元素
zadd key [NX|XX] [CH] [INCR] score member [score member ...]
深度解讀|Redis 避不開的五種數據結構

zadd 命令也是有返回值的,返回值就是當前 zadd 命令成功添加元素的個數。zadd 命令有很多選填參數:

  • nx: 元素必須不存在時,才可以設置成功。
  • xx: 元素必須存在時,才可以設置成功。
  • ch: 返回此命令執行完成後,有序集合元素和分數發生變化的個數
  • incr: 對 score 做增加。

備註:由於有序集合相比集合提供了排序字段,正是因為如此也付出了相應的代價,sadd 的時間複雜度為 O(1),而 zadd 的時間複雜度為O(log(n))。

  • 計算成員個數
zcard key
深度解讀|Redis 避不開的五種數據結構

  • 計算某個成員的分數
zscore key member
深度解讀|Redis 避不開的五種數據結構

在使用 zscore 命令時,如果 key 不存在,或者元素不存在時,該命令返回的都是(nil)。

  • 計算成員的排名
zrank key member
zrevrank key member
深度解讀|Redis 避不開的五種數據結構

zrank 命令是從分數低到高排名,而 zrevrank 命令則恰恰相反,從高到低排名。有一點要特別注意, zrank 和 zrevrank 命令與 zscore 是命令不同的,前者通過分數計算出最後的排名,而後者則是直接返回當前元素的分數。

  • 刪除元素
zrem key member [member ...]
深度解讀|Redis 避不開的五種數據結構

返回的結果為成功刪除元素的個數,因為 zrem 命令是支持批量刪除的。

  • 增加元素分數
zincrby key increment member
深度解讀|Redis 避不開的五種數據結構

雖然 zincrby 命令是增加元素分數的,但我們也可以指定負數,這樣當前元素的分數,則會相減。

  • 返回指定排名範圍的元素
zrange key start stop [WITHSCORES]
zrevrange key start stop [WITHSCORES]
深度解讀|Redis 避不開的五種數據結構

zrange 命令是通過分數從低到高返回數據,而 zrevrange 命令是通過分數從高到低返回數據。如果執行命令時添加了 WITHSCORES 可選參數,則返回數據時會返回當前元素的分數。

  • 返回指定分數範圍的元素
zrangebyscore key min max [WITHSCORES] [LIMIT offset count]
zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count]
深度解讀|Redis 避不開的五種數據結構

min 和 max 參數還支持開區間(小括號)和閉區間(中括號),同時我們還可以用 -inf 和 +inf 參數代表無限小和無限大。

深度解讀|Redis 避不開的五種數據結構

  • 返回指定分數範圍元素個數
zcount key min max
深度解讀|Redis 避不開的五種數據結構

  • 刪除指定排名內的升序元素
zremrangebyrank key start stop
深度解讀|Redis 避不開的五種數據結構

  • 刪除指定分數範圍元素
zremrangebyscore key min max
深度解讀|Redis 避不開的五種數據結構

2.集合間操作

  • 交集
zinterstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]

zinterstore 命令參數比較多:

  • destination:將交集的計算結果,保存到這個鍵中。
  • numkeys:需要做交集計算鍵的個數。
  • key [key ...]:需要做交集計算的鍵。
  • WEIGHTS weight:每個鍵的權重,在做交集計算時,每個鍵中的分數值都會乘以這個權重,默認每個鍵的權重為 1。
  • AGGREGATE SUM|MIN|MAX:計算成員交集後,分值可以按照 sum(和)、min(最小值)、max(最大值)做彙總,默認值為 sum。
深度解讀|Redis 避不開的五種數據結構

下面我們將權重設置為 0.5,這樣當計算交集後,有序集合中的元素分數將都會減半,並且使用 max 參數彙總。

深度解讀|Redis 避不開的五種數據結構

  • 並集
zunionstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
深度解讀|Redis 避不開的五種數據結構

zunionstore 命令的相關參數和 zinterstore 命令相同。

時間複雜度

深度解讀|Redis 避不開的五種數據結構

內部編碼

有序集合類型的內部編碼有兩種,它們分別是:

  • ziplist(壓縮列表):當有序集合的元素個數小於 128 個(默認設置),同時每個元素的值都小於 64 字節(默認設置),Redis 會採用 ziplist 作為有序集合的內部實現。
  • skiplist(跳躍表):當上述條件不滿足時,Redis 會採用 skiplist 作為內部編碼。

備註:上述中的默認值,也可以通過以下參數設置:zset-max-ziplist-entries 和 zset-max-ziplist-value。

下面我們用以下示例來驗證上述結論。

當元素個數比較少,並且每個元素也比較小時,內部編碼為 ziplist:

深度解讀|Redis 避不開的五種數據結構

當元素個數超過 128 時,內部編碼為 skiplist。下面我們將採用 python 動態創建128個元素,下面為源碼:

import redis
r = redis.Redis(host='127.0.0.1', port=6379)
if r.object('encoding', 'zsetkey') != None:
print('Key為【zsetkey】的字節編碼為【%s】' % r.object('encoding', 'zsetkey').decode('utf-8'))
for i in range(1, 600):
r.zadd('zsetkey',i,1)
if r.object('encoding', 'zsetkey') != None:
print('Key為【zsetkey】的字節編碼為【%s】' % r.object('encoding', 'zsetkey').decode('utf-8'))
Key為【zsetkey】的字節編碼為【ziplist】
Key為【zsetkey】的字節編碼為【skiplist】

當有序集合中有任何一個元素大於 64 個字節時,內部編碼為 skiplist。

import redis
r = redis.Redis(host='127.0.0.1', port=6379)
if r.object('encoding', 'zsetkey') != None:
print('Key為【zsetkey】的字節編碼為【%s】' % r.object('encoding', 'zsetkey').decode('utf-8'))
value = ''
for i in range(1, 600):
value += str(i)
r.zadd('zsetkey',value,1)
if r.object('encoding', 'zsetkey') != None:
print('Key為【zsetkey】的字節編碼為【%s】' % r.object('encoding', 'zsetkey').decode('utf-8'))
Key為【zsetkey】的字節編碼為【skiplist】
深度解讀|Redis 避不開的五種數據結構

到這裡,本文就結束了,寫了這麼多,其實主要大部分是關於命令的簡單介紹,其中也介紹了一些關鍵要點,如有不正確的地方,歡迎留言。

裴彬,Java 工程師,喜歡編程,自己開發的博客系統已上線:jilinwula.com

每日一博欄目,每日為你推薦優秀博主的優質技術文章。同時歡迎用戶投稿,文章一旦被官方賬號收錄,我們會在網站首頁等位置進行推薦哦。關注開源中國OSC每日獲取優質推送,點擊“瞭解更多”閱讀原文章。


分享到:


相關文章: