Redis 中有 5 種資料結構,分別是字串(String)、雜湊(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set),因為使用 Redis 場景的開發中肯定是無法避開這些基礎結構的,所以熟練掌握它們也就成了一項必不可少的能力。本文章精要地介紹了 Redis 的這幾種資料結構,主要覆蓋了它們各自的定義、基本用法與相關要點。
字串型別
字串是 Redis 中的最基礎的資料結構,我們儲存到 Redis 中的 key,也就是鍵,就是字串結構的。除此之外,Redis 中其它資料結構也是在字串的基礎上設計的,可見字串結構對於 Redis 是多麼重要。
Redis 中的字串結構可以儲存多種資料型別,如:簡單的字串、JSON、XML、二進位制等,但有一點要特別注意:在 Redis 中字串型別的值最大隻能儲存 512 MB。
命令
下面通過命令瞭解一下對字串型別的操作:
1.設定值
1 |
set key value [EX seconds] [PX milliseconds] [NX|XX] |
set 命令有幾個非必須的選項,下面我們看一下它們的具體說明:
- EX seconds:為鍵設定秒級過期時間
- PX milliseconds:為鍵設定毫秒級過期時間
- NX:鍵必須不存在,才可以設定成功,用於新增
- XX:鍵必須存在,才可以設定成功,用於更新
set 命令帶上可選引數 NX 和 XX 在實際開發中的作用與 setnx 和 setxx 命令相同。我們知道 setnx 命令只有當 key 不存在的時候才能設定成功,換句話說,也就是同一個 key 在執行 setnx 命令時,只能成功一次,並且由於 Redis 的單執行緒命令處理機制,即使多個客戶端同時執行 setnx 命令,也只有一個客戶端執行成功。所以,基於 setnx 這種特性,setnx 命令可以作為分散式鎖的一種解決方案。
而 setxx 命令則可以在安全性比較高的場景中使用,因為 set 命令執行時,會執行覆蓋的操作,而 setxx 在更新 key 時可以確保該 key 已經存在了,所以為了保證 key 中資料型別的正確性,可以使用 setxx 命令。
2.獲取值
1 |
get key |
3.批量設定值
1 |
mset key value |
4.批量獲取值
1 |
mget key |
如果有些鍵不存在,那麼它的值將為 nil,也就是空,並且返回結果的順序與傳入時相同。
5.計數
1 |
incr key |
incr 命令用於對值做自增操作,返回的結果分為 3 種情況:
- 如果值不是整數,那麼返回的一定是錯誤
- 如果值是整數,那麼返回自增後的結果
- 如果鍵不存在,那麼就會建立此鍵,然後按照值為 0 自增, 就是返回 1
除了有 incr 自增命令外,Redis 中還提供了其它對數字處理的命令。例如:
1 2 3 4 |
decr key 自減 incrby kek increment 自增指定數字 decrby key decrement 自減指定數字 incrbyfloat key increment 自增浮點數 |
6.追加值
1 |
append key value |
append 命令可以向字串尾部追加值。
7.字串長度
1 |
strlen key |
由於每個中文佔用 3 個位元組,所以 jilinwula 這個鍵,返回是字串長度為 12,而不是 4。
8.設定並返回原值
1 |
getset key value |
9.設定指定位置的字元
1 |
setrange key offeset value |
10.獲取部分字串
1 |
getrange key start end |
時間複雜度
在 Redis 中執行任何命令時,都有相應的時間複雜度,複雜度越高也就越費時間,所以在執行 Redis 中的命令時,如果要執行的命令複雜度越高,就越要慎重。下面是字串命令時間複雜度型別表:
命令 | 時間複雜度 |
set key value | O(1) |
get key | O(1) |
del key | O(k) k是鍵的個數 |
mset key value | O(k) k是鍵的個數 |
mget key | O(k) k是鍵的個數 |
incr key | O(1) |
decr key | O(1) |
incrby key increment | O(1) |
decrby keky increment | O(1) |
incrbyfloat key iincrement | O(1) |
append key value | O(1) |
strlen key | O(1) |
setrange key offset value | O(1) |
getrange key start end | O(n) n是字串長度 |
內部編碼
在 Redis 中字串型別的內部編碼有 3 種:
- int:8 個位元組的長整型
- embstr:小於等於 39 個位元組的字串
- raw:大於 39 個位元組的字串
雜湊型別
大部分語言基本都提供了雜湊型別,如 Java 語言中的 Map 型別及 Python 語言中的字典型別等等。雖然語言不同,但它們基本使用都是一樣的,也就是都是鍵值對結構的。例如:
1 |
value={{field1, value1} |
通過下圖可以直觀感受一下字串型別和雜湊型別的區別:
Redis 中雜湊型別都是鍵值對結構的,所以要特別注意這裡的 value 並不是指 Redis 中 key 的 value,而是雜湊型別中的 field 所對應的 value。
命令
下面我們還是和介紹字串型別一樣,瞭解一下 Redis 中雜湊型別的相關命令。
1.設定值
1 |
hset key field value |
我們看上圖執行的命令知道,hset 命令也是有返回值的。如果 hset 命令設定成功,則返回 1,否則返回 0。除此之外 Redis 也為雜湊型別提供了 hsetnx 命令。在前文對字串的介紹中,我們知道 nx 命令只有當 key 不存在的時候,才能設定成功,同樣的,hsetnx 命令在 field 不存在的時候,才能設定成功。
2.獲取值
1 |
hget key field |
我們看 hget 命令和 get 有很大的不同,get 命令在獲取的時候,只要寫一個名字就可以了,而 hget 命令則要寫兩個名字,第一個名字是 key,第二個名字是 field。當然 key 或者 field 不存在時,返回的結果都是 nil。
3.刪除 field
1 |
hdel key field [field ...] |
hdel 命令刪除的時候,也會有返回值,並且這個返回就是成功刪除 field 的個數。當 field 不存在時,並不會報錯,而是直接返回 0。
4.計算 field 個數
1 |
hlen key |
hlen 命令返回的就是當前 key 中 field 的個數,如果 key 不存在,則返回 0。
5.批量設定或獲取 field-value
1 2 |
hmget key field [field ...] hmset key field value [field value ...] |
hmset 命令和 hmget 命令分別是批量設定和獲取值的,hmset 命令沒有什麼要注意的,但 hmget 命令要特別注意,當我們獲取一個不存在的 key 或者不存在的 field 時,Redis 並不會報錯,而是返回 nil。並且有幾個 field 不存在,則 Redis 返回幾個 nil。
6.判斷 field 是否存在
1 |
hexists key field |
當執行 hexists 命令時,如果當前 key 包括 field,則返回 1,否則返回 0。
7.獲取所有 field
1 |
hkeys key |
8.獲取所有 value
1 |
hvals key |
9.獲取所有的 field-value
1 |
hgetall key |
hgetall 命令會返回當前 key 中的所有 field-value,並按照順序依次返回。
10.計數
1 2 |
hincrby key field increment hincrbyfloat key field increment |
hincrby 命令和 incrby 命令的使用功能基本一樣,都是對值進行增量操作的,唯一不同的就是 incrby 命令的作用域是 key,而 hincrby 命令的作用域則是 field。
11.計算 value 的字串長度
1 |
hstrlen key field |
hstrlen 命令返回的是當前 key 中 field 中字串的長度,如果當前 key 中沒有 field 則返回 0。
時間複雜度
命令 | 時間複雜度 |
hset key field value | O(1) |
hget key field | O(1) |
hdel key field [field …] | O(k) ,k是field個數 |
hlen key | O(1) |
hgetall key | O(n) ,n是field總數 |
hmget key field [field …] | O(k) ,k是field個數 |
hmset key field value [field value …] | O(k) ,k是field個數 |
hexists key field | O(1) |
hkeys key | O(n) ,n是field總數 |
hvals key | O(n) ,n是field總數 |
hsetnx key field value | O(1) |
hincrby key field increment | O(1) |
hincrbyfloat key field increment | O(1) |
hstrlen key field | O(1) |
內部編碼
Redis 雜湊型別的內部編碼有兩種,它們分別是:
- ziplist(壓縮列表):當雜湊型別中元素個數小於 hash-max-ziplist-entries 配置(預設 512 個),同時所有值都小於 hash-max-ziplist-value 配置(預設 64 位元組)時,Redis 會使用 ziplist 作為雜湊的內部實現。
- hashtable(雜湊表):當上述條件不滿足時,Redis 則會採用 hashtable 作為雜湊的內部實現。
下面我們通過以下命令來演示一下 ziplist 和 hashtable 這兩種內部編碼。
當 field 個數比較少並且 value 也不是很大時候 Redis 雜湊型別的內部編碼為 ziplist:
當 value 中的位元組數大於 64 位元組時(可以通過 hash-max-ziplist-value 設定),內部編碼會由 ziplist 變成 hashtable。
當 field 個數超過 512(可以通過 hash-max-ziplist-entries 引數設定),內部編碼也會由 ziplist 變成 hashtable。
由於直接手動建立 512 個 field 不方便,為了更好的驗證該功能,我將用程式的方式,動態建立 512 個 field 來驗證此功能,下面為具體的程式碼:
1 2 3 4 5 6 7 8 |
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 列表型別的特點如下:
- 列表中所有的元素都是有序的,所以它們是可以通過索引獲取的,也就是上圖中的 lindex 命令。並且在 Redis 中列表型別的索引是從 0 開始的。
- 列表中的元素是可以重複的,也就是說在 Redis 列表型別中,可以儲存同名元素,如下圖所示:
命令
下面我們還是和學習其它資料型別一樣,我們還是先學習一下 Redis 列表型別的命令。
1.新增操作
- 從右邊插入元素
1 |
rpush key value [value ...] |
我們看 rpush 命令在插入時,是有返回值的,返回值的數量就是當前列表中所有元素的個數。
我們也可以用下面的命令從左到右獲取當前列表中的所有的元素,也就是如上圖所示中那樣。
1 |
lrange 0 -1 |
- 從左邊插入元素
1 |
lpush key value [value ...] |
lpush 命令的返回值及用法和 rpush 命令一樣。通過上面的事例證明了我們前面說的,rpush 命令和 lpush 命令的返回值並不是當前插入元素的個數,而是當前 key 中全部元素的個數,因為當前 key 中已經有了 3 個元素,所以我們在執行插入命令時,返回的就是 6 而不是 3,。
- 向某個元素前或者後插入元素
1 |
linsert key BEFORE|AFTER pivot value |
linsert 命令在執行的時候首先會從當前列表中查詢到 pivot 元素,其次再將這個新元素插入到 pivot 元素的前面或者後面。並且我們通過上圖可以知道 linsert 命令在執行成功後也是會有返回值的,返回的結果就是當前列表中元素的個數。
2.查詢
- 獲取指定範圍內的元素列表
1 |
lrange key start stop |
lrange 命令會獲取列表中指定索引範圍的所有元素。
通過索引獲取列表主要有兩個特點:
- 索引下標從左到右分別是 0 到 N-1,從右到左是 -1 到 -N。
- lrange 命令中的 stop 引數在執行時會包括當前元素,並不是所有的語言都是這樣的。我們要獲取列表中前兩個元素則可以如下圖所示:
- 獲取列表中指定索引下標的元素
1 |
lindex key index |
- 獲取列表長度
1 |
llen key |
3.刪除
- 從列表左側彈出元素
1 |
lpop key |
lpop 命令執行成功後會返回當前被刪除的元素名稱。
- 從列表右側彈出元素
1 |
rpop key |
rpop 命令和 lpop 命令的使用方式一樣。
- 刪除指定元素
1 |
lrem key count value |
lrem 命令會將列表中等於 value 的元素刪除掉,並且會根據 count 引數來決定刪除 value 的元素個數。
下面我們看一下 count 引數的使用說明:
count > 0:表示從左到右,最多刪除 count 個元素。也就是如下圖所示:
我們看上圖中的命令中,雖然我們將 count 引數指定的是 5,將 value 引數指定的是 2,但由於當前列表中只有一個 2,所以,當前 lrem 命令最多隻能刪除 1 個元素,並且 lrem 命令也是有返回值的,也就是當前成功刪除元素的個數。
count < 0:從右到左,最多刪除 count 個元素。
count = 0:刪除所有元素。
- 按照索引範圍修剪列表
1 |
ltrim key start stop |
ltrim 命令會直接保留 start 索引到 stop 索引的之間的元素,幷包括當前元素,而之外的元素則都會刪除掉,所以該命令也叫修剪列表。
並且有一點要注意,ltrim 命令不會返回當前的列表中元素的個數,而是返回改命令是否成功的狀態。
4.修改
- 修改指定索引下標的元素
1 |
lset key index value |
5.阻塞操作
1 2 |
blpop key [key ...] timeout brpop key [key ...] timeout |
blpop 和 brpop 命令是 lpop 和 rpop 命令的阻塞版本,它們除了彈出方向不同以外,使用方法基本相同。
- key [key …]:可以指定多個列表的鍵。
- timeout:阻塞時間(單位:秒)
下面我們看一下該命令的詳細使用。
列表為空:如果 timeout=3,則表示客戶端等待 3 秒後才能返回結果,如果 timeout=0,則表示客戶端會一直等待,也就是會阻塞。
由於我期間向列表中插入了元素,否則上述命令將一直阻塞下去。
列表不為空:如果 timeout=0,並且列表不為空時,則 blpop 和 brpop 命令會立即返回結果,不會阻塞。
下面我們看一下 blpop 和 brpop 命令的注意事項。
如果使用 blpop 和 brpop 命令指定多個鍵時,blpop 和 brpop 命令會從左到右遍歷鍵,並且一旦有一個鍵能返回元素,則客戶端會立即返回。
當列表為空時,上述命令會阻塞,如果向上述中的任何一個鍵中插入元素,則上述命令會直接返回該鍵的元素。
如果多個客戶端都對同一個鍵執行 blpop 或者 brpop 命令,則最先執行該命令的客戶端會獲取到該鍵的元素。
我同時啟動了 3 個客戶端,因為當前列表為空,所以上述命令執行後會阻塞。如果此時我向該列表中插入元素,則只有第一個客戶端會有返回結果,因為第一個客戶端是第一個執行上述命令的。
時間複雜度
下面我們看一下列表中命令的相關時間複雜度。
操作型別 | 命令 | 時間複雜度 |
新增 | rpush key value [value …] | O(k),k是元素的個數 |
新增 | lpush key value [value …] | O(k),k是元素的個數 |
新增 | linsert key BEFORE/AFTER pivot value | O(n),n是pivot距離列表頭或者尾的距離 |
查詢 | lrange key start stop | O(s + n),s是start偏移量,n是start到stop的範圍 |
查詢 | lindex key index | O(n),n是索引的偏移量 |
查詢 | llen key | O(1) |
刪除 | lpop key | O(1) |
刪除 | rpop key | O(1) |
刪除 | lrem key count value | O(n),n是列表長度 |
刪除 | ltrim key start stop | O(n),n是要裁剪的元素個數 |
修改 | lset key index value | O(n),n是索引的偏移量 |
阻塞操作 | blpop/brpop key [key …] timeout | O(1) |
內部編碼
列表中的內部編碼有兩種,它們分別是:
- 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.集合內操作
- 新增元素
1 |
sadd key member [member ...] |
sadd 命令也是有返回值的,它的返回值就是當前執行 sadd 命令成功新增元素的個數,因為 set 中不能儲存重複元素,所以在執行 sadd setkey c d 命令時,返回的是 1,而不是 2。因為元素 c 已經成功儲存到 set 中,不能再儲存了,只能將 d 儲存到 set 中。
- 刪除元素
1 |
srem key member [member ...] |
srem 命令和 sadd 命令一樣也是有返回值的,返回值就是當前刪除元素的個數。
- 計算元素個數
1 |
scard key |
scard 命令的時間複雜度為O(1),scard 命令不會遍歷 set 中的所有元素,而是直接使用 Redis 中的內部變數。
- 判讀元素是否在集合中
1 |
sismember key member |
sismember 命令也有返回值,如果返回值為1則表示當前元素在當前 set 中,如果返回 0 則表示當前元素不在 set 中。
- 隨機從 set 中返回指定個數元素
1 |
srandmember key [count] |
srandmember 命令中有一個可選引數 count,count 引數指的是返回元素的個數,如果當前 set 中的元素個數小於 count,則 srandmember 命令返回當前 set 中的所有元素,如果 count 引數等於 0,則不返回任何資料,如果 count 引數小於 0,則隨機返回當前 count 個數的元素。
- 從集合中隨機彈出元素
1 |
spop key [count] |
spop 命令也是隨機從 set 中彈出元素,並且也支援 count 可選引數,但有一點和 srandmember 命令不同。spop 命令在隨機彈出元素之後,會將彈出的元素從 set 中刪除。
- 獲取所有元素
1 |
smembers key |
smembers 命令雖然能獲取當前 set 中所有的元素,但返回元素的順序與 sadd 新增元素的順序不一定相同,這也就是前面提到過的儲存在 set 中的元素是無序的。
2.集合間操作
- 集合的交集
1 |
sinter key [key ...] |
- 集合的並集
1 |
sunion key [key ...] |
- 集合的差集
1 |
sdiff key [key ...] |
- 將集合的交集、並集、差集的結果儲存
1 2 3 |
sinterstore destination key [key ...] sunionstore destination key [key ...] sdiffstore destination key [key ...] |
為什麼 Redis 要提供 sinterstore、sunionstore、sdiffstore 命令來將集合的交集、並集、差集的結果儲存起來呢?這是因為 Redis 在進行上述比較時,會比較耗費時間,所以為了提高效能可以將交集、並集、差集的結果提前儲存起來,這樣在需要使用時,可以直接通過 smembers 命令獲取。
時間複雜度
下面我們看一下 set 中相關命令的時間複雜度。
命令 | 時間複雜度 |
sadd key member [member …] | O(k),k是元素的個數 |
srem key member [member …] | O(k),k是元素的個數 |
scard key | O(1) |
sismember key member | O(1) |
srandmember key [count] | O(count) |
spop key [count] | O(1) |
smembers key | O(n),n是元素的總數 |
sinter key [key …] | O(m * k),k是多個集合中元素最少的個數,m是鍵個數 |
sunion key [key …] | O(k),k是多個元素個數和 |
sdiff key [key …] | O(k),k是多個元素個數和 |
sinterstore destination key [key …] | O(m * k),k是多個集合中元素最少的個數,m是鍵個數 |
sunionstore destination key [key …] | O(k),k是多個元素個數和 |
sdiffstore destination key [key …] | O(k),k是多個元素個數和 |
內部編碼
- intset(整數集合):當集合中的元素都是整數,並且集合中的元素個數小於 512 個時,Redis 會選用 intset 作為底層內部實現。
- hashtable(雜湊表):當上述條件不滿足時,Redis 會採用 hashtable 作為底層實現。
備註:我們可以通過 set-max-intset-entries 引數來設定上述中的預設引數。
下面我們看一下具體的事例,來驗證我們上面提到的內部編碼。
當元素個數較少並且都是整數時,內部編碼為 intset。
當元素不全是整數時,內部編碼為 hashtable。
當元素個數超過 512 個時,內部編碼為 hashtable。
1 2 3 4 5 6 7 8 9 10 11 |
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),而查詢時可以通過分數計算元素的排名,然後再返回結果。因為有序集合也是集合型別,所以有序集合中也是不插入重複元素的,但在有序集合中分數則是可以重複,那如果在有序集合中有多個元素的分數是相同的,這些重複元素的排名是怎麼計算的呢?後邊我們再做詳細說明。
下面先看一下列表、集合、有序集合三種資料型別之間的區別:
資料結構 | 是否允許重複元素 | 是否有序 | 有序實現方式 | 應用場景 |
列表 | 是 | 是 | 索引下標 | 時間軸、訊息佇列 |
集合 | 否 | 否 | 無 | 標籤、社交 |
有序集合 | 否 | 是 | 分數 | 排行榜、社交 |
命令
1.集合內操作
- 新增元素
1 |
zadd key [NX|XX] [CH] [INCR] score member [score member ...] |
zadd 命令也是有返回值的,返回值就是當前 zadd 命令成功新增元素的個數。zadd 命令有很多選填引數:
- nx: 元素必須不存在時,才可以設定成功。
- xx: 元素必須存在時,才可以設定成功。
- ch: 返回此命令執行完成後,有序集合元素和分數發生變化的個數
- incr: 對 score 做增加。
備註:由於有序集合相比集合提供了排序欄位,正是因為如此也付出了相應的代價,sadd 的時間複雜度為 O(1),而 zadd 的時間複雜度為O(log(n))。
- 計算成員個數
1 |
zcard key |
- 計算某個成員的分數
1 |
zscore key member |
在使用 zscore 命令時,如果 key 不存在,或者元素不存在時,該命令返回的都是(nil)。
- 計算成員的排名
1 2 |
zrank key member zrevrank key member |
zrank 命令是從分數低到高排名,而 zrevrank 命令則恰恰相反,從高到低排名。有一點要特別注意, zrank 和 zrevrank 命令與 zscore 是命令不同的,前者通過分數計算出最後的排名,而後者則是直接返回當前元素的分數。
- 刪除元素
1 |
zrem key member [member ...] |
返回的結果為成功刪除元素的個數,因為 zrem 命令是支援批量刪除的。
- 增加元素分數
1 |
zincrby key increment member |
雖然 zincrby 命令是增加元素分數的,但我們也可以指定負數,這樣當前元素的分數,則會相減。
- 返回指定排名範圍的元素
1 2 |
zrange key start stop [WITHSCORES] zrevrange key start stop [WITHSCORES] |
zrange 命令是通過分數從低到高返回資料,而 zrevrange 命令是通過分數從高到低返回資料。如果執行命令時新增了 WITHSCORES 可選引數,則返回資料時會返回當前元素的分數。
- 返回指定分數範圍的元素
1 2 |
zrangebyscore key min max [WITHSCORES] [LIMIT offset count] zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count] |
min 和 max 引數還支援開區間(小括號)和閉區間(中括號),同時我們還可以用 -inf 和 +inf 引數代表無限小和無限大。
- 返回指定分數範圍元素個數
1 |
zcount key min max |
- 刪除指定排名內的升序元素
1 |
zremrangebyrank key start stop |
- 刪除指定分數範圍元素
1 |
zremrangebyscore key min max |
2.集合間操作
- 交集
1 |
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。
下面我們將權重設定為 0.5,這樣當計算交集後,有序集合中的元素分數將都會減半,並且使用 max 引數彙總。
- 並集
1 |
zunionstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX] |
zunionstore 命令的相關引數和 zinterstore 命令相同。
時間複雜度
命令 | 時間複雜度 |
zadd key [NX/XX] [CH] [INCR] score member [score member …] | O(k*log(n)),k是新增元素的個數,n是當前有序集合元素個數 |
zcard key | O(1) |
zscore key member | O(1) |
zrank key member | O(log(n)),n是當前有序集合元素個數 |
zrevrank key member | O(log(n)),n是當前有序集合元素個數 |
zrem key member [member …] | O(k*log(n)),k是刪除元素的個數,n是當前有序集合元素個數 |
zincrby key increment member | O(log(n)),n是當前有序集合元素個數 |
zrange key start stop [WITHSCORES] | O(log(n) + k),k是要獲取的元素個數,n是當前有序集合個數 |
zrevrange key start stop [WITHSCORES] | O(log(n) + k),k是要獲取的元素個數,n是當前有序集合個數 |
zrangebyscore key min max [WITHSCORES] [LIMIT offset count] | O(log(n) + k),k是要獲取的元素個數,n是當前有序集合個數 |
zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count] | O(log(n) + k),k是要獲取的元素個數,n是當前有序集合個數 |
zcount key min max | O(log(n)),n是當前有序集合元素個數 |
zremrangebyrank key start stop | O(log(n) + k),k是要刪除的元素個數,n是當前有序集合個數 |
zremrangebyscore key min max | O(log(n) + k),k是要刪除的元素個數,n是當前有序集合個數 |
zinterstore destination numkeys key [key …] [WEIGHTS weight] [AGGREGATE SUM/MIN/MAX] | O(n k) + O(m log(m)),n是元素元素最小的有序集合元素個數,k是有序集合個數,m是結果集中元素個數 |
zunionstore destination numkeys key [key …] [WEIGHTS weight] [AGGREGATE SUM/MIN/MAX] | O(n) + O(m * log(m)),n是所有有序集合元素個數和,m是結果集中元素個數。 |
內部編碼
有序集合型別的內部編碼有兩種,它們分別是:
- ziplist(壓縮列表):當有序集合的元素個數小於 128 個(預設設定),同時每個元素的值都小於 64 位元組(預設設定),Redis 會採用 ziplist 作為有序集合的內部實現。
- skiplist(跳躍表):當上述條件不滿足時,Redis 會採用 skiplist 作為內部編碼。
備註:上述中的預設值,也可以通過以下引數設定:zset-max-ziplist-entries 和 zset-max-ziplist-value。
下面我們用以下示例來驗證上述結論。
當元素個數比較少,並且每個元素也比較小時,內部編碼為 ziplist:
當元素個數超過 128 時,內部編碼為 skiplist。下面我們將採用 python 動態建立128個元素,下面為原始碼:
1 2 3 4 5 6 7 8 9 10 11 |
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。
1 2 3 4 5 6 7 8 9 10 11 12 |
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】 |
到這裡,本文就結束了,寫了這麼多,其實主要大部分是關於命令的簡單介紹,其中也介紹了一些關鍵要點,如有不正確的地方,歡迎留言。
作者介紹
裴彬,Java 工程師,喜歡程式設計,自己開發的部落格系統已上線:jilinwula.com