Redis 避不開的五種資料結構
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 中還提供了其它對數字處理的命令。例如:
1234 | 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
12 | 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.計數
12 | hincrby key field incrementhincrbyfloat 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 來驗證此功能,下面為具體的程式碼:
12345678 | import redisr=redis.Redis(host='127.0.0.1',port=6379)print('Key為【userinfo】的位元組編碼為【%s】'%r.object('encoding','userinfo').decode('utf-8'))foriinrange(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 | lrange0-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 引數在執行時會包括當前元素,並不是所有的語言都是這樣的。我們要獲取列表中前兩個