Redis 中的 5 種數(shù)據(jù)結(jié)構(gòu)精講(1)
Redis 中有 5 種數(shù)據(jù)結(jié)構(gòu),分別是字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set),因?yàn)槭褂?Redis 場(chǎng)景的開(kāi)發(fā)中肯定是無(wú)法避開(kāi)這些基礎(chǔ)結(jié)構(gòu)的,所以熟練掌握它們也就成了一項(xiàng)必不可少的能力。
字符串類型
字符串是 Red ?is 中的最基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),我們保存到 Redis 中的 key,也就是鍵,就是字符串結(jié)構(gòu)的。除此之外,Redis 中其它數(shù)據(jù)結(jié)構(gòu)也是在字符串的基礎(chǔ)上設(shè)計(jì)的,可見(jiàn)字符串結(jié)構(gòu)對(duì)于 Redis 是多么重要。
Redis 中的字符串結(jié)構(gòu)可以保存多種數(shù)據(jù)類型,如:簡(jiǎn)單的字符串、JSON、XML、二進(jìn)制等,但有一點(diǎn)要特別注意:在 Redis 中字符串類型的值最大只能保存 512 MB。
?
命令
下面通過(guò)命令了解一下對(duì)字符串類型的操作:
?
1.設(shè)置值
1 | set key value [EX seconds] [PX milliseconds] [NX|XX] |
set 命令有幾個(gè)非必須的選項(xiàng),下面我們看一下它們的具體說(shuō)明:
EX seconds:為鍵設(shè)置秒級(jí)過(guò)期時(shí)間
PX milliseconds:為鍵設(shè)置毫秒級(jí)過(guò)期時(shí)間
NX:鍵必須不存在,才可以設(shè)置成功,用于添加
XX:鍵必須存在,才可以設(shè)置成功,用于更新
set 命令帶上可選參數(shù) NX 和 XX 在實(shí)際開(kāi)發(fā)中的作用與 setnx 和 setxx 命令相同。我們知道 setnx 命令只有當(dāng)?key 不存在的時(shí)候才能設(shè)置成功,換句話說(shuō),也就是同一個(gè) key 在執(zhí)行 setnx 命令時(shí),只能成功一次,并且由于 Redis 的單線程命令處理機(jī)制,即使多個(gè)客戶端同時(shí)執(zhí)行 setnx 命令,也只有一個(gè)客戶端執(zhí)行成功。所以,基于 setnx 這種特性,setnx 命令可以作為分布式鎖的一種解決方案。
而 setxx 命令則可以在安全性比較高的場(chǎng)景中使用,因?yàn)?set 命令執(zhí)行時(shí),會(huì)執(zhí)行覆蓋的操作,而 setxx 在更新 key 時(shí)可以確保該 key 已經(jīng)存在了,所以為了保證 key 中數(shù)據(jù)類型的正確性,可以使用 setxx 命令。
?
2.獲取值
1 | get key |
?
3.批量設(shè)置值
1 | mset key value |
?
4.批量獲取值
1 | mget key |
如果有些鍵不存在,那么它的值將為 nil,也就是空,并且返回結(jié)果的順序與傳入時(shí)相同。
?
5.計(jì)數(shù)
1 | incr key |
incr 命令用于對(duì)值做自增操作,返回的結(jié)果分為 3 種情況:
如果值不是整數(shù),那么返回的一定是錯(cuò)誤
如果值是整數(shù),那么返回自增后的結(jié)果
如果鍵不存在,那么就會(huì)創(chuàng)建此鍵,然后按照值為 0 自增, 就是返回 1
除了有 incr 自增命令外,Redis 中還提供了其它對(duì)數(shù)字處理的命令。例如:
1 2 3 4 | decr key 自減 incrby kek increment 自增指定數(shù)字 decrby key decrement 自減指定數(shù)字 incrbyfloat key increment 自增浮點(diǎn)數(shù) |
?
6.追加值
1 | append key value |
append 命令可以向字符串尾部追加值。
?
7.字符串長(zhǎng)度
1 | strlen key |
由于每個(gè)中文占用 3 個(gè)字節(jié),所以 jilinwula 這個(gè)鍵,返回是字符串長(zhǎng)度為 12,而不是 4。
?
8.設(shè)置并返回原值
1 | getset key value |
?
9.設(shè)置指定位置的字符
1 | setrange key offeset value |
?
10.獲取部分字符串
1 | getrange key start end |
?
時(shí)間復(fù)雜度
在 Redis 中執(zhí)行任何命令時(shí),都有相應(yīng)的時(shí)間復(fù)雜度,復(fù)雜度越高也就越費(fèi)時(shí)間,所以在執(zhí)行 Redis 中的命令時(shí),如果要執(zhí)行的命令復(fù)雜度越高,就越要慎重。下面是字符串命令時(shí)間復(fù)雜度類型表:
命令 | 時(shí)間復(fù)雜度 |
set key value | O(1) |
get key | O(1) |
del key | O(k) k是鍵的個(gè)數(shù) |
mset key value | O(k) k是鍵的個(gè)數(shù) |
mget key | O(k) k是鍵的個(gè)數(shù) |
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是字符串長(zhǎng)度 |
?
內(nèi)部編碼
在 Redis 中字符串類型的內(nèi)部編碼有 3 種:
int:8 個(gè)字節(jié)的長(zhǎng)整型
embstr:小于等于 39 個(gè)字節(jié)的字符串
raw:大于 39 個(gè)字節(jié)的字符串
?
哈希類型
大部分語(yǔ)言基本都提供了哈希類型,如 Java 語(yǔ)言中的 Map 類型及 Python 語(yǔ)言中的字典類型等等。雖然語(yǔ)言不同,但它們基本使用都是一樣的,也就是都是鍵值對(duì)結(jié)構(gòu)的。例如:
1 | value={{field1, value1} |
通過(guò)下圖可以直觀感受一下字符串類型和哈希類型的區(qū)別:
Redis 中哈希類型都是鍵值對(duì)結(jié)構(gòu)的,所以要特別注意這里的 value 并不是指 Redis 中 key 的 value,而是哈希類型中的 field 所對(duì)應(yīng)的 value。
?
命令
下面我們還是和介紹字符串類型一樣,了解一下 Redis 中哈希類型的相關(guān)命令。
?
1.設(shè)置值
1 | hset key field value |
我們看上圖執(zhí)行的命令知道,hset 命令也是有返回值的。如果 hset 命令設(shè)置成功,則返回 1,否則返回 0。除此之外 Redis 也為哈希類型提供了 hsetnx 命令。在前文對(duì)字符串的介紹中,我們知道 nx 命令只有當(dāng) key 不存在的時(shí)候,才能設(shè)置成功,同樣的,hsetnx 命令在 field 不存在的時(shí)候,才能設(shè)置成功。
?
2.獲取值
1 | hget key field |
我們看 hget 命令和 get 有很大的不同,get 命令在獲取的時(shí)候,只要寫(xiě)一個(gè)名字就可以了,而 hget 命令則要寫(xiě)兩個(gè)名字,第一個(gè)名字是 key,第二個(gè)名字是 field。當(dāng)然 key 或者 field 不存在時(shí),返回的結(jié)果都是 nil。
?
3.刪除 field
1 | hdel key field [field ...] |
hdel 命令刪除的時(shí)候,也會(huì)有返回值,并且這個(gè)返回就是成功刪除 field 的個(gè)數(shù)。當(dāng) field 不存在時(shí),并不會(huì)報(bào)錯(cuò),而是直接返回 0。
?
4.計(jì)算 field 個(gè)數(shù)
1 | hlen key |
hlen 命令返回的就是當(dāng)前 key 中 field 的個(gè)數(shù),如果 key 不存在,則返回 0。
?
5.批量設(shè)置或獲取 field-value
1 2 | hmget key field [field ...] hmset key field value [field value ...] |
hmset 命令和 hmget 命令分別是批量設(shè)置和獲取值的,hmset 命令沒(méi)有什么要注意的,但 hmget 命令要特別注意,當(dāng)我們獲取一個(gè)不存在的 key 或者不存在的 field 時(shí),Redis 并不會(huì)報(bào)錯(cuò),而是返回 nil。并且有幾個(gè) field 不存在,則 Redis 返回幾個(gè) nil。
?
6.判斷 field 是否存在
1 | hexists key field |
當(dāng)執(zhí)行 hexists 命令時(shí),如果當(dāng)前 key 包括 field,則返回 1,否則返回 0。
?
7.獲取所有 field
1 | hkeys key |
?
8.獲取所有 value
1 | hvals key |
?
9.獲取所有的 field-value
1 | hgetall key |
hgetall 命令會(huì)返回當(dāng)前 key 中的所有 field-value,并按照順序依次返回。
?
10.計(jì)數(shù)
1 2 | hincrby key field increment hincrbyfloat key field increment |
hincrby 命令和 incrby 命令的使用功能基本一樣,都是對(duì)值進(jìn)行增量操作的,唯一不同的就是 incrby 命令的作用域是 key,而 hincrby 命令的作用域則是 field。
?
11.計(jì)算 value 的字符串長(zhǎng)度
1 | hstrlen key field |
hstrlen 命令返回的是當(dāng)前 key 中 field 中字符串的長(zhǎng)度,如果當(dāng)前 key 中沒(méi)有 field 則返回 0。
?
時(shí)間復(fù)雜度
命令 | 時(shí)間復(fù)雜度 |
hset key field value | O(1) |
hget key field | O(1) |
hdel key field [field …] | O(k) ,k是field個(gè)數(shù) |
hlen key | O(1) |
hgetall key | O(n) ,n是field總數(shù) |
hmget key field [field …] | O(k) ,k是field個(gè)數(shù) |
hmset key field value [field value …] | O(k) ,k是field個(gè)數(shù) |
hexists key field | O(1) |
hkeys key | O(n) ,n是field總數(shù) |
hvals key | O(n) ,n是field總數(shù) |
hsetnx key field value | O(1) |
hincrby key field increment | O(1) |
hincrbyfloat key field increment | O(1) |
hstrlen key field | O(1) |
?
內(nèi)部編碼
Redis 哈希類型的內(nèi)部編碼有兩種,它們分別是:
ziplist(壓縮列表):當(dāng)哈希類型中元素個(gè)數(shù)小于 hash-max-ziplist-entries 配置(默認(rèn) 512 個(gè)),同時(shí)所有值都小于 hash-max-ziplist-value 配置(默認(rèn) 64 字節(jié))時(shí),Redis 會(huì)使用 ziplist 作為哈希的內(nèi)部實(shí)現(xiàn)。
hashtable(哈希表):當(dāng)上述條件不滿足時(shí),Redis 則會(huì)采用 hashtable 作為哈希的內(nèi)部實(shí)現(xiàn)。
下面我們通過(guò)以下命令來(lái)演示一下 ziplist 和 hashtable 這兩種內(nèi)部編碼。
當(dāng) field 個(gè)數(shù)比較少并且 value 也不是很大時(shí)候 Redis 哈希類型的內(nèi)部編碼為 ziplist:
當(dāng) value 中的字節(jié)數(shù)大于 64 字節(jié)時(shí)(可以通過(guò) hash-max-ziplist-value 設(shè)置),內(nèi)部編碼會(huì)由 ziplist 變成 hashtable。
當(dāng) field 個(gè)數(shù)超過(guò) 512(可以通過(guò) hash-max-ziplist-entries 參數(shù)設(shè)置),內(nèi)部編碼也會(huì)由 ziplist 變成 hashtable。
由于直接手動(dòng)創(chuàng)建 512 個(gè) field 不方便,為了更好的驗(yàn)證該功能,我將用程序的方式,動(dòng)態(tài)創(chuàng)建 512 個(gè) field 來(lái)驗(yàn)證此功能,下面為具體的代碼:
1 2 3 4 5 6 7 8 | import redis r = redis.Redis(host='127.0.0.1', port=6379) print('Key為【userinfo】的字節(jié)編碼為【%s】' % r.object('encoding', 'userinfo').decode('utf-8')) for i in range(1,513):???? ????r.hset('userinfo', i, '吉林烏拉') print('Key為【userinfo】的字節(jié)編碼為【%s】' % r.object('encoding', 'userinfo').decode('utf-8')) Key為【userinfo】的字節(jié)編碼為【ziplist】 Key為【userinfo】的字節(jié)編碼為【hashtable】 |
?
列表類型
Redis 中列表類型可以簡(jiǎn)單地理解為存儲(chǔ)多個(gè)有序字符串的一種新類型,這種類型除了字符串類型中已有的功能外,還提供了其它功能,如可以對(duì)列表的兩端插入和彈出元素(在列表中的字符串都可以稱之為元素),除此之外還可以獲取指定的元素列表,并且還可以通過(guò)索引下標(biāo)獲取指定元素等等。下面我們通過(guò)下圖來(lái)看一下 Redis 中列表類型的插入和彈出操作:
下面我們看一下 Redis 中列表類型的獲取與刪除操作:
Redis 列表類型的特點(diǎn)如下:
列表中所有的元素都是有序的,所以它們是可以通過(guò)索引獲取的,也就是上圖中的 lindex 命令。并且在 Redis 中列表類型的索引是從 0 開(kāi)始的。
列表中的元素是可以重復(fù)的,也就是說(shuō)在 Redis 列表類型中,可以保存同名元素,如下圖所示:
?