Redis簡單數(shù)據(jù)結(jié)構(gòu)及適用場景記錄
1、五種基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)
Redis 所有的數(shù)據(jù)結(jié)構(gòu)都是以唯一的 key 字符串作為名稱,然后通過這個唯一 key 值來獲取相應(yīng)的 value 數(shù)據(jù)。不同類型的數(shù)據(jù)結(jié)構(gòu)的差異就在于 value 的結(jié)構(gòu)不一樣。
建議體驗(yàn)在線操作redis,官網(wǎng)http://try.redis.io/
①String 字符串
常見的用途就是緩存用戶信息,我們將用戶信息結(jié)構(gòu)體使用 JSON 序列化成字符串,然后將序列化后的字符串塞進(jìn) Redis 來緩存。同樣,取用戶信息會經(jīng)過一次反序列化的過程。
Redis 的字符串是動態(tài)字符串,是可以修改的字符串,內(nèi)部結(jié)構(gòu)實(shí)現(xiàn)上類似于 Java 的ArrayList,采用預(yù)分配冗余空間的方式來減少內(nèi)存的頻繁分配。
內(nèi)部為當(dāng)前字符串實(shí)際分配的空間 capacity 一般要高于實(shí)際字符串長度 len。當(dāng)字符串長度小于 1M 時, 擴(kuò)容都是加倍現(xiàn)有的空間,如果超過 1M,擴(kuò)容時一次只會多擴(kuò) 1M 的空間。需要注意的是字符串最大長度為 512M。
命令舉例(set key value,get key)
簡單設(shè)置,刪除,計(jì)數(shù)
set name hello
OK
get name
"hello"
exists name
(integer) 1
del name
(integer) 1
get name
(nil)
合并操作
set name hello
OK
get name
"hello"
set name1 world
OK
set name2 !
OK
mget name name1 name2
- "hello"
- "world"
- "!"
批量更新
mset name boy name1 girl name2 unknown
OK
mget name name1 name2
- "boy"
- "girl"
- "unknown"
過期和 set 命令擴(kuò)展
get name
"hello"
expire name 2
(integer) 1
get name
(nil)
setex等效于set +expire
setex name 5 wahah
OK
get name
"wahah"
get name
(nil)
計(jì)數(shù)
set age 30
OK
incr age
(integer) 31
incrby age 10
(integer) 41
incrby age -10
(integer) 31
如果一個字符串已經(jīng)設(shè)置了過期時間,然后你調(diào)用了
set 方法修改了它,它的過期時間會消失。
②List (列表)
Redis 的列表相當(dāng)于 Java 語言里面的 LinkedList,注意它是鏈表而不是數(shù)組。這意味著
list 的插入和刪除操作非???,時間復(fù)雜度為 O(1),但是索引定位很慢,時間復(fù)雜度為O(n),這點(diǎn)讓人非常意外。
當(dāng)列表彈出了最后一個元素之后,該數(shù)據(jù)結(jié)構(gòu)自動被刪除,內(nèi)存被回收。
Redis 的列表結(jié)構(gòu)常用來做異步隊(duì)列使用。將需要延后處理的任務(wù)結(jié)構(gòu)體序列化成字符串塞進(jìn) Redis 的列表,另一個線程從這個列表中輪詢數(shù)據(jù)進(jìn)行處理。
key = books value=python java golang
lindex 相當(dāng)于 Java 鏈表的get(int index)方法,它需要對鏈表進(jìn)行遍歷,性能隨著參數(shù)
index 增大而變差。
ltrim 和字面上的含義不太一樣,個人覺得它叫 lretain(保留) 更合適一些,因?yàn)?ltrim 跟的兩個參數(shù)start_index 和end_index 定義了一個區(qū)間,在這個區(qū)間內(nèi)的值,
ltrim 要保留,區(qū)間之外統(tǒng)統(tǒng)砍掉。
rpush books python java golang
(integer) 3
lindex books 1
"java"
lindex books 0
"python"
ltrim books 0 1
OK
lindex books 0
"python"
lindex books 1
"java"
lindex books 2
(nil)
lrange books 0 -1 # 獲取所有元素,O(n) 慎用 1)
"python"
③hash (字典)
Redis 的字典相當(dāng)于 Java 語言里面的 HashMap,它是無序字典。內(nèi)部實(shí)現(xiàn)結(jié)構(gòu)上同
Java 的 HashMap 也是一致的,同樣的數(shù)組 + 鏈表二維結(jié)構(gòu)。第一維 hash 的數(shù)組位置碰撞時,就會將碰撞的元素使用鏈表串接起來。
不同的是,Redis 的字典的值只能是字符串,另外它們 rehash 的方式不一樣,因?yàn)?br>
Java 的 HashMap 在字典很大時,rehash 是個耗時的操作,需要一次性全部 rehash。Redis
為了高性能,不能堵塞服務(wù),所以采用了漸進(jìn)式 rehash 策略。
漸進(jìn)式 rehash 會在 rehash 的同時,保留新舊兩個 hash 結(jié)構(gòu),查詢時會同時查詢兩個
hash 結(jié)構(gòu),然后在后續(xù)的定時任務(wù)中以及 hash 的子指令中,循序漸進(jìn)地將舊 hash 的內(nèi)容一點(diǎn)點(diǎn)遷移到新的 hash 結(jié)構(gòu)中。
當(dāng) hash 移除了最后一個元素之后,該數(shù)據(jù)結(jié)構(gòu)自動被刪除,內(nèi)存被回收。
hash 結(jié)構(gòu)也可以用來存儲用戶信息,不同于字符串一次性需要全部序列化整個對象,
hash 可以對用戶結(jié)構(gòu)中的每個字段單獨(dú)存儲。這樣當(dāng)我們需要獲取用戶信息時可以進(jìn)行部分獲取。而以整個字符串的形式去保存用戶信息的話就只能一次性全部讀取,這樣就會比較浪費(fèi)網(wǎng)絡(luò)流量。
hash 也有缺點(diǎn),hash 結(jié)構(gòu)的存儲消耗要高于單個字符串,到底該使用 hash 還是字符串,需要根據(jù)實(shí)際情況再三權(quán)衡。
hset bookss golang "concurrency in go"
(integer) 1
hset bookss java "think in java"
(integer) 1
hset bookss python "python cookbook"
(integer) 1
hgetall bookss
- "golang"
- "concurrency in go"
- "java"
- "think in java"
- "python"
- "python cookbook"
hlen books (integer) 3
hget books java "think in java"
hset books golang "learning go programming" # 因?yàn)槭歉虏僮?,所以返?0 (integer) 0
hget books golang "learning go programming"
hmset books java "effective java" python "learning python" golang "modern golang programming" # 批量 set
OK
④set (集合)
Redis 的集合相當(dāng)于 Java 語言里面的 HashSet,它內(nèi)部的鍵值對是無序的唯一的。它的內(nèi)部實(shí)現(xiàn)相當(dāng)于一個特殊的字典,字典中所有的 value 都是一個值NULL。
當(dāng)集合中最后一個元素移除之后,數(shù)據(jù)結(jié)構(gòu)自動刪除,內(nèi)存被回收。 set 結(jié)構(gòu)可以用來存儲活動中獎的用戶 ID,因?yàn)橛腥ブ毓δ?,可以保證同一個用戶不會中獎兩次。
sadd books python
(integer) 1
sadd books python # 重復(fù)
(integer) 0
sadd books java golang
(integer) 2
smembers books # 注意順序,和插入的并不一致,因?yàn)?set 是無序的
- "java"
- "python"
- "golang"
sismember books java # 查詢某個 value 是否存在,相當(dāng)于 contains(o)
(integer) 1
sismember books rust
(integer) 0
scard books # 獲取長度相當(dāng)于 count()
(integer) 3
spop books # 彈出一個
"java"
⑤zset (有序列表)
zset 可能是 Redis 提供的最為特色的數(shù)據(jù)結(jié)構(gòu),它也是在面試中面試官最愛問的數(shù)據(jù)結(jié)構(gòu)。它類似于 Java 的 SortedSet 和 HashMap 的結(jié)合體,一方面它是一個 set,保證了內(nèi)部
value 的唯一性,另一方面它可以給每個 value 賦予一個 score,代表這個 value 的排序權(quán)重。它的內(nèi)部實(shí)現(xiàn)用的是一種叫著「跳躍列表」的數(shù)據(jù)結(jié)構(gòu)。
zset 中最后一個 value 被移除后,數(shù)據(jù)結(jié)構(gòu)自動刪除,內(nèi)存被回收。 zset 可以用來存粉絲列表,value 值是粉絲的用戶 ID,score 是關(guān)注時間。我們可以對粉絲列表按關(guān)注時間進(jìn)行排序。
zset 還可以用來存儲學(xué)生的成績,value 值是學(xué)生的 ID,score 是他的考試成績。我們可以對成績按分?jǐn)?shù)進(jìn)行排序就可以得到他的名次。
zadd books 9.0 "think in java"
(integer) 1
zadd books 8.9 "java concurrency"
(integer) 1
zadd books 8.6 "java cookbook"
(integer) 1
zrange books 0 -1 # 按 score 排序列出,參數(shù)區(qū)間為排名范圍
- "java cookbook"
- "java concurrency"
- "think in java"
zrevrange books 0 -1 # 按 score 逆序列出,參數(shù)區(qū)間為排名范圍
- "think in java"
- "java concurrency"
- "java cookbook"
zcard books # 相 當(dāng) 于 count()
(integer) 3
zscore books "java concurrency" # 獲取指定 value 的 score
"8.9000000000000004" # 內(nèi)部 score 使用 double 類型進(jìn)行存儲,所以存在小數(shù)點(diǎn)精度問題
zrank books "java concurrency" # 排名
(integer) 1
zrangebyscore books 0 8.91 # 根據(jù)分值區(qū)間遍歷 zset
- "java cookbook"
- "java concurrency"
zrangebyscore books -inf 8.91 withscores # 根據(jù)分值區(qū)間 (-∞, 8.91] 遍歷 zset,同時返回分值。inf 代表 infinite,無窮大的意思。
- "java cookbook"
- "8.5999999999999996"
- "java concurrency" 4) "8.9000000000000004"
zrem books "java concurrency" # 刪 除 value
(integer) 1
zrange books 0 -1
- "java cookbook"
- "think in java"
參考資料
--------------------Redis深度歷險:核心原理和應(yīng)用實(shí)踐