命令
下面我們還是和學習其它數(shù)據(jù)類型一樣,我們還是先學習一下 Redis 列表類型的命令。
?
1.添加操作
從右邊插入元素
1 | rpush?key?value?[value?...] |
我們看 rpush 命令在插入時,是有返回值的,返回值的數(shù)量就是當前列表中所有元素的個數(shù)。
我們也可以用下面的命令從左到右獲取當前列表中的所有的元素,也就是如上圖所示中那樣。
1 | lrange?0?-1 |
從左邊插入元素
1 | lpush?key?value?[value?...] |
lpush 命令的返回值及用法和 rpush 命令一樣。通過上面的事例證明了我們前面說的,rpush 命令和 lpush 命令的返回值并不是當前插入元素的個數(shù),而是當前 key 中全部元素的個數(shù),因為當前 key 中已經(jīng)有了 3 個元素,所以我們在執(zhí)行插入命令時,返回的就是 6 而不是 3,。
向某個元素前或者后插入元素
1 | linsert?key?BEFORE|AFTER?pivot?value |
linsert 命令在執(zhí)行的時候首先會從當前列表中查找到 pivot 元素,其次再將這個新元素插入到 pivot 元素的前面或者后面。并且我們通過上圖可以知道 linsert 命令在執(zhí)行成功后也是會有返回值的,返回的結果就是當前列表中元素的個數(shù)。
?
2.查找
獲取指定范圍內(nèi)的元素列表
1 | lrange?key?start?stop |
lrange 命令會獲取列表中指定索引范圍的所有元素。
通過索引獲取列表主要有兩個特點:
索引下標從左到右分別是 0 到 N-1,從右到左是 -1 到 -N。
lrange 命令中的 stop 參數(shù)在執(zhí)行時會包括當前元素,并不是所有的語言都是這樣的。我們要獲取列表中前兩個元素則可以如下圖所示:
獲取列表中指定索引下標的元素
1 | lindex?key?index |
獲取列表長度
1 | llen?key |
?
3.刪除
從列表左側彈出元素
1 | lpop?key |
lpop 命令執(zhí)行成功后會返回當前被刪除的元素名稱。
從列表右側彈出元素
1 | rpop?key |
rpop 命令和 lpop 命令的使用方式一樣。
刪除指定元素
1 | lrem?key?count?value |
lrem 命令會將列表中等于 value 的元素刪除掉,并且會根據(jù) count 參數(shù)來決定刪除 value 的元素個數(shù)。
下面我們看一下 count 參數(shù)的使用說明:
count > 0:表示從左到右,最多刪除 count 個元素。也就是如下圖所示:
我們看上圖中的命令中,雖然我們將 count 參數(shù)指定的是 5,將 value 參數(shù)指定的是 2,但由于當前列表中只有一個 2,所以,當前 lrem 命令最多只能刪除 1 個元素,并且 lrem 命令也是有返回值的,也就是當前成功刪除元素的個數(shù)。
count < 0:從右到左,最多刪除 count 個元素。
count = 0:刪除所有元素。
按照索引范圍修剪列表
1 | ltrim?key?start?stop |
ltrim 命令會直接保留 start 索引到 stop 索引的之間的元素,并包括當前元素,而之外的元素則都會刪除掉,所以該命令也叫修剪列表。
并且有一點要注意,ltrim 命令不會返回當前的列表中元素的個數(shù),而是返回改命令是否成功的狀態(tài)。
?
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 命令會從左到右遍歷鍵,并且一旦有一個鍵能返回元素,則客戶端會立即返回。
當列表為空時,上述命令會阻塞,如果向上述中的任何一個鍵中插入元素,則上述命令會直接返回該鍵的元素。
如果多個客戶端都對同一個鍵執(zhí)行 blpop 或者 brpop 命令,則最先執(zhí)行該命令的客戶端會獲取到該鍵的元素。
我同時啟動了 3 個客戶端,因為當前列表為空,所以上述命令執(zhí)行后會阻塞。如果此時我向該列表中插入元素,則只有第一個客戶端會有返回結果,因為第一個客戶端是第一個執(zhí)行上述命令的。
?
時間復雜度
下面我們看一下列表中命令的相關時間復雜度。
| 操作類型 | 命令 | 時間復雜度 |
| 添加 | rpush key value [value …] | O(k),k是元素的個數(shù) |
| 添加 | lpush key value [value …] | O(k),k是元素的個數(shù) |
| 添加 | 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是要裁剪的元素個數(shù) |
| 修改 | lset key index value | O(n),n是索引的偏移量 |
| 阻塞操作 | blpop/brpop key [key …] timeout | O(1) |
?
內(nèi)部編碼
列表中的內(nèi)部編碼有兩種,它們分別是:
ziplist(壓縮列表):當列表中元素個數(shù)小于 512(默認)個,并且列表中每個元素的值都小于 64(默認)個字節(jié)時,Redis 會選擇用 ziplist 來作為列表的內(nèi)部實現(xiàn)以減少內(nèi)存的使用。當然上述默認值也可以通過相關參數(shù)修改:list-max-ziplist-entried(元素個數(shù))、list-max-ziplist-value(元素值)。
linkedlist(鏈表):當列表類型無法滿足 ziplist 條件時,Redis 會選擇用 linkedlist 作為列表的內(nèi)部實現(xiàn)。
?
集合類型
Redis 中的集合類型,也就是 set。在 Redis 中 set 也是可以保存多個字符串的,經(jīng)常有人會分不清 list 與 set,下面我們重點介紹一下它們之間的不同:
set 中的元素是不可以重復的,而 list 是可以保存重復元素的。
set 中的元素是無序的,而 list 中的元素是有序的。
set 中的元素不能通過索引下標獲取元素,而 list 中的元素則可以通過索引下標獲取元素。
除此之外 set 還支持更高級的功能,例如多個 set 取交集、并集、差集等。
?
命令
下面我們介紹一下 set 中的相關命令。
?
1.集合內(nèi)操作
添加元素
1 | sadd?key?member?[member?...] |
sadd 命令也是有返回值的,它的返回值就是當前執(zhí)行 sadd 命令成功添加元素的個數(shù),因為 set 中不能保存重復元素,所以在執(zhí)行?sadd setkey c d?命令時,返回的是 1,而不是 2。因為元素 c 已經(jīng)成功保存到 set 中,不能再保存了,只能將 d 保存到 set 中。
刪除元素
1 | srem?key?member?[member?...] |
srem 命令和 sadd 命令一樣也是有返回值的,返回值就是當前刪除元素的個數(shù)。
計算元素個數(shù)
1 | scard?key |
scard 命令的時間復雜度為O(1),scard 命令不會遍歷 set 中的所有元素,而是直接使用 Redis 中的內(nèi)部變量。
判讀元素是否在集合中
1 | sismember?key?member |
sismember 命令也有返回值,如果返回值為1則表示當前元素在當前 set 中,如果返回 0 則表示當前元素不在 set 中。
隨機從 set 中返回指定個數(shù)元素
1 | srandmember?key?[count] |
srandmember 命令中有一個可選參數(shù) count,count 參數(shù)指的是返回元素的個數(shù),如果當前 set 中的元素個數(shù)小于 count,則 srandmember 命令返回當前 set 中的所有元素,如果 count 參數(shù)等于 0,則不返回任何數(shù)據(jù),如果 count 參數(shù)小于 0,則隨機返回當前 count 個數(shù)的元素。
從集合中隨機彈出元素
1 | spop?key?[count] |
spop 命令也是隨機從 set 中彈出元素,并且也支持 count 可選參數(shù),但有一點和 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是元素的個數(shù) |
| srem key member [member …] | O(k),k是元素的個數(shù) |
| scard key | O(1) |
| sismember key member | O(1) |
| srandmember key [count] | O(count) |
| spop key [count] | O(1) |
| smembers key | O(n),n是元素的總數(shù) |
| sinter key [key …] | O(m * k),k是多個集合中元素最少的個數(shù),m是鍵個數(shù) |
| sunion key [key …] | O(k),k是多個元素個數(shù)和 |
| sdiff key [key …] | O(k),k是多個元素個數(shù)和 |
| sinterstore destination key [key …] | O(m * k),k是多個集合中元素最少的個數(shù),m是鍵個數(shù) |
| sunionstore destination key [key …] | O(k),k是多個元素個數(shù)和 |
| sdiffstore destination key [key …] | O(k),k是多個元素個數(shù)和 |
?
內(nèi)部編碼
intset(整數(shù)集合):當集合中的元素都是整數(shù),并且集合中的元素個數(shù)小于 512 個時,Redis 會選用 intset 作為底層內(nèi)部實現(xiàn)。
hashtable(哈希表):當上述條件不滿足時,Redis 會采用 hashtable 作為底層實現(xiàn)。
備注:我們可以通過 set-max-intset-entries 參數(shù)來設置上述中的默認參數(shù)。
下面我們看一下具體的事例,來驗證我們上面提到的內(nèi)部編碼。
當元素個數(shù)較少并且都是整數(shù)時,內(nèi)部編碼為 intset。
當元素不全是整數(shù)時,內(nèi)部編碼為 hashtable。
當元素個數(shù)超過 512 個時,內(nèi)部編碼為 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】的字節(jié)編碼為【%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】的字節(jié)編碼為【%s】'?%?r.object('encoding',?'setkey').decode('utf-8')) Key為【setkey】的字節(jié)編碼為【intset】 Key為【setkey】的字節(jié)編碼為【hashtable】 |
?
有序集合類型
看名字我們就知道,有序集合也是一種集合,并且這個集合還是有序的。列表也是有序的,那它和有序集合又有什么不同呢?有序集合的有序和列表的有序是不同的。列表中的有序指的的是插入元素的順序和查詢元素的順序相同,而有序集合中的有序指的是它會為每個元素設置一個分數(shù)(score),而查詢時可以通過分數(shù)計算元素的排名,然后再返回結果。因為有序集合也是集合類型,所以有序集合中也是不插入重復元素的,但在有序集合中分數(shù)則是可以重復,那如果在有序集合中有多個元素的分數(shù)是相同的,這些重復元素的排名是怎么計算的呢?后邊我們再做詳細說明。
下面先看一下列表、集合、有序集合三種數(shù)據(jù)類型之間的區(qū)別:
| 數(shù)據(jù)結構 | 是否允許重復元素 | 是否有序 | 有序實現(xiàn)方式 | 應用場景 |
| 列表 | 是 | 是 | 索引下標 | 時間軸、消息隊列 |
| 集合 | 否 | 否 | 無 | 標簽、社交 |
| 有序集合 | 否 | 是 | 分數(shù) | 排行榜、社交 |
?
命令
?
1.集合內(nèi)操作
添加元素
1 | zadd?key?[NX|XX]?[CH]?[INCR]?score?member?[score?member?...] |
zadd 命令也是有返回值的,返回值就是當前 zadd 命令成功添加元素的個數(shù)。zadd 命令有很多選填參數(shù):
nx: 元素必須不存在時,才可以設置成功。
xx: 元素必須存在時,才可以設置成功。
ch: 返回此命令執(zhí)行完成后,有序集合元素和分數(shù)發(fā)生變化的個數(shù)
incr: 對 score 做增加。
備注:由于有序集合相比集合提供了排序字段,正是因為如此也付出了相應的代價,sadd 的時間復雜度為 O(1),而 zadd 的時間復雜度為O(log(n))。
計算成員個數(shù)
1 | zcard?key |
計算某個成員的分數(shù)
1 | zscore?key?member |
在使用 zscore 命令時,如果 key 不存在,或者元素不存在時,該命令返回的都是(nil)。
計算成員的排名
1 2 | zrank?key?member zrevrank?key?member |
zrank 命令是從分數(shù)低到高排名,而 zrevrank 命令則恰恰相反,從高到低排名。有一點要特別注意, zrank 和 zrevrank 命令與 zscore 是命令不同的,前者通過分數(shù)計算出最后的排名,而后者則是直接返回當前元素的分數(shù)。
刪除元素
1 | zrem?key?member?[member?...] |
返回的結果為成功刪除元素的個數(shù),因為 zrem 命令是支持批量刪除的。
增加元素分數(shù)
1 | zincrby?key?increment?member |
雖然 zincrby 命令是增加元素分數(shù)的,但我們也可以指定負數(shù),這樣當前元素的分數(shù),則會相減。
返回指定排名范圍的元素
1 2 | zrange?key?start?stop?[WITHSCORES] zrevrange?key?start?stop?[WITHSCORES] |
zrange 命令是通過分數(shù)從低到高返回數(shù)據(jù),而 zrevrange 命令是通過分數(shù)從高到低返回數(shù)據(jù)。如果執(zhí)行命令時添加了 WITHSCORES 可選參數(shù),則返回數(shù)據(jù)時會返回當前元素的分數(shù)。
返回指定分數(shù)范圍的元素
1 2 | zrangebyscore?key?min?max?[WITHSCORES]?[LIMIT?offset?count] zrevrangebyscore?key?max?min?[WITHSCORES]?[LIMIT?offset?count] |
min 和 max 參數(shù)還支持開區(qū)間(小括號)和閉區(qū)間(中括號),同時我們還可以用 -inf 和 +inf 參數(shù)代表無限小和無限大。
返回指定分數(shù)范圍元素個數(shù)
1 | zcount?key?min?max |
刪除指定排名內(nèi)的升序元素
1 | zremrangebyrank?key?start?stop |
刪除指定分數(shù)范圍元素
1 | zremrangebyscore?key?min?max |
?
2.集合間操作
交集
1 | zinterstore?destination?numkeys?key?[key?...]?[WEIGHTS?weight]?[AGGREGATE?SUM|MIN|MAX] |
zinterstore 命令參數(shù)比較多:
destination:將交集的計算結果,保存到這個鍵中。
numkeys:需要做交集計算鍵的個數(shù)。
key [key …]:需要做交集計算的鍵。
WEIGHTS weight:每個鍵的權重,在做交集計算時,每個鍵中的分數(shù)值都會乘以這個權重,默認每個鍵的權重為 1。
AGGREGATE SUM|MIN|MAX:計算成員交集后,分值可以按照 sum(和)、min(最小值)、max(最大值)做匯總,默認值為? sum。
下面我們將權重設置為 0.5,這樣當計算交集后,有序集合中的元素分數(shù)將都會減半,并且使用 max 參數(shù)匯總。
并集
1 | zunionstore?destination?numkeys?key?[key?...]?[WEIGHTS?weight]?[AGGREGATE?SUM|MIN|MAX] |
zunionstore 命令的相關參數(shù)和 zinterstore 命令相同。
?
時間復雜度
| 命令 | 時間復雜度 |
| zadd key [NX/XX] [CH] [INCR] score member [score member …] | O(k*log(n)),k是添加元素的個數(shù),n是當前有序集合元素個數(shù) |
| zcard key | O(1) |
| zscore key member | O(1) |
| zrank key member | O(log(n)),n是當前有序集合元素個數(shù) |
| zrevrank key member | O(log(n)),n是當前有序集合元素個數(shù) |
| zrem key member [member …] | O(k*log(n)),k是刪除元素的個數(shù),n是當前有序集合元素個數(shù) |
| zincrby key increment member | O(log(n)),n是當前有序集合元素個數(shù) |
| zrange key start stop [WITHSCORES] | O(log(n) + k),k是要獲取的元素個數(shù),n是當前有序集合個數(shù) |
| zrevrange key start stop [WITHSCORES] | O(log(n) + k),k是要獲取的元素個數(shù),n是當前有序集合個數(shù) |
| zrangebyscore key min max [WITHSCORES] [LIMIT offset count] | O(log(n) + k),k是要獲取的元素個數(shù),n是當前有序集合個數(shù) |
| zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count] | O(log(n) + k),k是要獲取的元素個數(shù),n是當前有序集合個數(shù) |
| zcount key min max | O(log(n)),n是當前有序集合元素個數(shù) |
| zremrangebyrank key start stop | O(log(n) + k),k是要刪除的元素個數(shù),n是當前有序集合個數(shù) |
| zremrangebyscore key min max | O(log(n) + k),k是要刪除的元素個數(shù),n是當前有序集合個數(shù) |
| zinterstore destination numkeys key [key …] [WEIGHTS weight] [AGGREGATE SUM/MIN/MAX] | O(n?k) + O(m?log(m)),n是元素元素最小的有序集合元素個數(shù),k是有序集合個數(shù),m是結果集中元素個數(shù) |
| zunionstore destination numkeys key [key …] [WEIGHTS weight] [AGGREGATE SUM/MIN/MAX] | O(n) + O(m * log(m)),n是所有有序集合元素個數(shù)和,m是結果集中元素個數(shù)。 |
?
內(nèi)部編碼
有序集合類型的內(nèi)部編碼有兩種,它們分別是:
ziplist(壓縮列表):當有序集合的元素個數(shù)小于 128 個(默認設置),同時每個元素的值都小于 64 字節(jié)(默認設置),Redis 會采用 ziplist 作為有序集合的內(nèi)部實現(xiàn)。
skiplist(跳躍表):當上述條件不滿足時,Redis 會采用 skiplist 作為內(nèi)部編碼。
備注:上述中的默認值,也可以通過以下參數(shù)設置:zset-max-ziplist-entries 和 zset-max-ziplist-value。
下面我們用以下示例來驗證上述結論。
當元素個數(shù)比較少,并且每個元素也比較小時,內(nèi)部編碼為 ziplist:
當元素個數(shù)超過 128 時,內(nèi)部編碼為 skiplist。下面我們將采用 python 動態(tài)創(chuàng)建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】的字節(jié)編碼為【%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】的字節(jié)編碼為【%s】'?%?r.object('encoding',?'zsetkey').decode('utf-8')) Key為【zsetkey】的字節(jié)編碼為【ziplist】 Key為【zsetkey】的字節(jié)編碼為【skiplist】 |
當有序集合中有任何一個元素大于 64 個字節(jié)時,內(nèi)部編碼為 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】的字節(jié)編碼為【%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】的字節(jié)編碼為【%s】'?%?r.object('encoding',?'zsetkey').decode('utf-8')) Key為【zsetkey】的字節(jié)編碼為【skiplist】 |





