Redis 之(二) Redis的基本数据结构以及一些常用的操作
本篇內(nèi)容是Redis最簡(jiǎn)單最容易掌握的知識(shí),如果你已經(jīng)熟知了,就可以選擇跳過(guò)啦!
要體驗(yàn)Redis,那么首先你得安裝Redis,這邊的話我就只講一下Windows環(huán)境下的安裝與操作:
Window 下安裝
下載地址:https://github.com/MSOpenTech/redis/releases。
Redis 支持 32 位和 64 位。這個(gè)需要根據(jù)你系統(tǒng)平臺(tái)的實(shí)際情況選擇,這里我們下載?Redis-x64-xxx.zip壓縮包到 C 盤,解壓后,將文件夾重新命名為?redis。
打開(kāi)一個(gè)?cmd?窗口 使用cd命令切換目錄到?C:\redis?運(yùn)行?redis-server.exe redis.windows.conf?。
如果想方便的話,可以把 redis 的路徑加到系統(tǒng)的環(huán)境變量里,這樣就省得再輸路徑了,后面的那個(gè) redis.windows.conf 可以省略,如果省略,會(huì)啟用默認(rèn)的。輸入之后,會(huì)顯示如下界面:
這時(shí)候另啟一個(gè)cmd窗口,原來(lái)的不要關(guān)閉,不然就無(wú)法訪問(wèn)服務(wù)端了。
切換到redis目錄下運(yùn)行?redis-cli.exe -h 127.0.0.1 -p 6379?。
設(shè)置鍵值對(duì)?set myKey abc
取出鍵值對(duì)?get myKey
到這里,安裝就結(jié)束啦!同樣的Linux和Mac環(huán)境下的同學(xué)們可以在菜鳥(niǎo)教程上找到自己需要的,我就不搬磚了!
?
Redis 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)
? ? ? ?Redis常見(jiàn)的數(shù)據(jù)結(jié)構(gòu)一共有五種,分別是:string(字符串)、List(列表)、set(集合)、zset(有序集合)、hash(哈希)
? ? ? ?這五種基本數(shù)據(jù)結(jié)構(gòu)需要我們熟練掌握!在Redis中,所有的數(shù)據(jù)結(jié)構(gòu)都是以唯一的Key字符串作為名稱,然后通過(guò)這個(gè)唯一Key值來(lái)獲取相應(yīng)的value數(shù)據(jù)。不同類型的數(shù)據(jù)結(jié)構(gòu)的差異就在于value的結(jié)構(gòu)不一樣。
? ? ? ?string(字符串)
? ? ? ?字符串string是Redis最簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu)。
? ? ? ?字符串結(jié)構(gòu)的使用非常廣泛,常見(jiàn)的用途就是緩存用戶信息,我們將用戶信息結(jié)構(gòu)體使用JSON序列化成字符串,然后將序列化后的字符串塞進(jìn)Redis來(lái)緩存,當(dāng)我們?nèi)〉糜脩粜畔r(shí)會(huì)經(jīng)過(guò)一次反序列化的過(guò)程。
? ? ? ?Redis的字符串是動(dòng)態(tài)字符串,也就是說(shuō)可修改的字符串,結(jié)構(gòu)上類似于Java中的ArrayList,采用了預(yù)分配冗余空間的方式減少內(nèi)存的頻繁分配。上圖中,字符串實(shí)際分配的空間capacity一般是>字符串的長(zhǎng)度len的。在Redis中,字符串的長(zhǎng)度小于1M時(shí),擴(kuò)容都是加倍現(xiàn)有的空間,如果超過(guò)了1M,那么擴(kuò)容則只會(huì)增加1M的空間。而字符串的最大長(zhǎng)度為512M。
? ? ? ?鍵值對(duì)
> set name codehole //設(shè)置指定key的值 OK > get name //獲取指定key "codehole" > exists name (integer) 1 > del name (integer) 1 > get name (nil)? ? ? ?批量鍵值對(duì)
> set name1 codehole OK > set name2 holycoder OK > mget name1 name2 name3 # 返回一個(gè)列表 1) "codehole" 2) "holycoder" 3) (nil) > mset name1 boy name2 girl name3 unknown > mget name1 name2 name3 1) "boy" 2) "girl" 3) "unknown"...? ? ? ?過(guò)期和set命令擴(kuò)展
? ? ? ?我們可以對(duì)key設(shè)置過(guò)期時(shí)間,到點(diǎn)自動(dòng)刪除,一般我們通過(guò)這個(gè)功能來(lái)控制緩存的失效時(shí)間。
? ? ? ?
> set name c1 OK > get name "c1" > expire name 5 //設(shè)置五秒后過(guò)期 (integer) 1 //.....等待五秒 > get name (nil)> setex name 5 c1 //5s 后過(guò)期,等價(jià)于 set+expire > get name "c1" //.....等待五秒 > get name (nil)> setnx name c1 // 如果 name 不存在就執(zhí)行 set 創(chuàng)建 (integer) 1 > get name "c1" > setnx name c2 (integer) 0 // 因?yàn)?name 已經(jīng)存在,所以 set 創(chuàng)建不成功 > get name "c1" // 沒(méi)有改變...?
? ? ? ?計(jì)數(shù)
? ? ? ?如果value值是一個(gè)整數(shù),還可以對(duì)他進(jìn)行自增操作。自增是有范圍的,它的范圍是 signend long 的最大最小值,超過(guò)了這個(gè)值,Redis會(huì)報(bào)錯(cuò)。
> set age 30 OK > incr age //通過(guò)incr命令將key中存儲(chǔ)的數(shù)值增一,如果key不存在,key的值會(huì)被先初始化為0,然后再進(jìn)行INCR操作 (integer) 31 > incrby age 5 //通過(guò)incrby命令將key中存儲(chǔ)的數(shù)值加上指定的增加量,如果key不存在,key的值會(huì)被先初始化為0,然后再進(jìn)行INCR操作 (integer) 36 > incrby age -5 (integer) 31 > set c1 9223372036854775807 // Long.Max OK > incr c1 (error) ERR increment or decrement would overflow...? ? ? ?字符串由多個(gè)字節(jié)(Byte)組成,每個(gè)字節(jié)由8個(gè)bit組成,我們可以講一個(gè)字符串看成很多bit的組合,這就是bitmap(位圖)的數(shù)據(jù)結(jié)構(gòu)。
? ? ? list(列表)
? ? ? ?Redis的list列表相當(dāng)于Java中的LinkedList,它是鏈表而不是數(shù)組,意味著增刪快而檢索查詢慢。
? ? ? ?當(dāng) list 彈出了最后一個(gè)元素后,該數(shù)據(jù)結(jié)構(gòu)會(huì)被自動(dòng)刪除,內(nèi)存被回收。
??
? ? ? ?Redis的 list 結(jié)構(gòu)通常用來(lái)做異步隊(duì)列使用。將需要延后處理的任務(wù)結(jié)構(gòu)體序列化成字符串塞進(jìn) list 列表,另一個(gè)線程從這個(gè)列表中輪詢數(shù)據(jù)進(jìn)行處理。
? ? ? ? 先進(jìn)先出:隊(duì)列
? ? ? ?
> rpush book java (integer) 1 > rpush book python (integer) 2 > rpush book golong (integer) 3// 將一個(gè)或多個(gè)值 value 插入到列表 key 的表尾(最右邊) // 也可寫為// > rpush books python java golang > llen book//返回列表的長(zhǎng)度 (integer) 3 > lpop book "java" > lpop book "python" > lpop book//移除列表左側(cè)的頭元素 "golong"
? ? ? ?先進(jìn)后出:棧
> rpush book python java golang (integer) 3 > rpop book "golang" > rpop book "java" > rpop book "python" > rpop book (nil)...?
? ? ? ?慢操作
? ? ? ?lindex 相當(dāng)于Java鏈表的 get(int index) 方法,他需要對(duì)鏈表進(jìn)行遍歷,性能隨鏈表長(zhǎng)度的增大而變差。
? ? ? ?
redis> LPUSH mylist "World" (integer) 1redis> LPUSH mylist "Hello" (integer) 2redis> LINDEX mylist 0 "Hello"redis> LINDEX mylist -1 "World"redis> LINDEX mylist 3 //index不在 mylist 的區(qū)間范圍內(nèi) (nil)? ? ? ?Ltrim 和跟著的兩個(gè)參數(shù)start_index和end_index定義了一個(gè)區(qū)間,在這個(gè)區(qū)間內(nèi)的值,ltrim要保留,區(qū)間之外統(tǒng)統(tǒng)砍掉。我們可以通過(guò)ltrim來(lái)實(shí)現(xiàn)一個(gè)定長(zhǎng)的鏈表。(其實(shí)就是裁剪)
? ? ? ?index可以為負(fù)數(shù),index=-1表示倒數(shù)第一個(gè)元素,同樣index=-2表示倒數(shù)第二個(gè)元素。
? ? ? ?Lrange 則是返回指定區(qū)間的元素。
> RPUSH mylist "hello" (integer) 1 > RPUSH mylist "hello" (integer) 2 > RPUSH mylist "foo" (integer) 3 > RPUSH mylist "bar" (integer) 4 > LTRIM mylist 1 -1 OK > LRANGE mylist 0 -1 1) "hello" 2) "foo" 3) "bar"? ? ? ?快速列表
? ? ? ?Redis的底層存儲(chǔ)還不是一個(gè)簡(jiǎn)單的linkedList,而是稱之為快速鏈表 ??quicklis的一個(gè)結(jié)構(gòu)。
? ? ? ?
?
? ? ? ?在列表元素較少的情況下會(huì)使用一塊連續(xù)的內(nèi)存存儲(chǔ),這個(gè)結(jié)構(gòu)是ziplist即壓縮列表。
? ? ? ?它將所有的元素緊挨著一起存儲(chǔ),分配的是一塊連續(xù)的內(nèi)存。當(dāng)數(shù)據(jù)較多的時(shí)候會(huì)改成quicklist。因?yàn)槠胀ǖ逆湵硇枰母郊又羔樋臻g太大,會(huì)浪費(fèi)空間,同時(shí)會(huì)加重內(nèi)存的碎片化。
? ? ? ?例如,一個(gè)列表中存儲(chǔ)的只是 int 類型的數(shù)據(jù),結(jié)構(gòu)上還需要兩個(gè)額外的指針prev和next。所以Redis將鏈表和ziplist結(jié)合起來(lái)組成了quicklist。也就是將多個(gè)ziplist使用雙向指針穿起來(lái)使用。這樣既滿足了快速的插入刪除性能,又不會(huì)出現(xiàn)太大的空間冗余。
?
? ? ? ?hash(字典)
? ? ? ?Redis的hash相當(dāng)于Java中的HashMap,是無(wú)序的。內(nèi)部結(jié)構(gòu)上也和HashMap是一致的,同樣是數(shù)組+鏈表的結(jié)構(gòu)。
?
?
? ? ? ?但是和HashMap不一樣的是,Redis中的hash的值只能是字符串,另外他們的rehash的方式不一樣,因?yàn)镴ava的HashMap在散列表很大時(shí),rehash時(shí)一個(gè)耗時(shí)的操作,需要一次性全部rehash。而Redis為了高性能,不能堵塞服務(wù),所以采取了漸進(jìn)式rehash策略。
? ? ? ?漸進(jìn)式rehash會(huì)在rehash的同時(shí),保留新舊兩個(gè)hash結(jié)構(gòu),查詢時(shí)會(huì)同時(shí)查詢兩個(gè)hash結(jié)構(gòu),然后在后續(xù)的定時(shí)任務(wù)中以及hash操作指令中,循序漸進(jìn)的將舊hash的內(nèi)容一點(diǎn)一點(diǎn)的遷移到新的hash結(jié)構(gòu)中。當(dāng)遷移完成了,就會(huì)用新的hash結(jié)構(gòu)取而代之。
? ? ? ?再簡(jiǎn)單一點(diǎn)講,就是Redis在做擴(kuò)容時(shí),拷貝節(jié)點(diǎn)數(shù)據(jù)的過(guò)程全部平攤到后續(xù)的操作中,而不是一次性拷貝,而我們想要實(shí)現(xiàn)這樣的平攤,就必須對(duì)節(jié)點(diǎn)進(jìn)行操作,例如再次插入,查找,修改,刪除時(shí)都會(huì)進(jìn)行拷貝。
? ? ? ?當(dāng)hash移除了最后一個(gè)元素之后,該數(shù)據(jù)結(jié)構(gòu)就會(huì)被自動(dòng)刪除,內(nèi)存被回收。
? ? ? ?hash結(jié)構(gòu)也可以用來(lái)存儲(chǔ)用戶信息,不同于字符串一次性要全部序列化整個(gè)對(duì)象,hash可以對(duì)用戶結(jié)構(gòu)中的每個(gè)字段單獨(dú)存儲(chǔ)。這樣我們需要獲取用戶信息時(shí)就可以進(jìn)行部分獲取。而用整個(gè)字符串的形式去保存用戶信息的話,就只能一次性全部獲取,比較浪費(fèi)網(wǎng)絡(luò)流量。
? ? ? ?當(dāng)然,hash也是有缺點(diǎn)的,hash的存儲(chǔ)結(jié)構(gòu)消耗要高于單個(gè)字符串,使用hash或者字符串時(shí),需要根據(jù)實(shí)際的情況再三權(quán)衡。
> hset books java "think in java" //命令行的字符串如果包含空格,要用引號(hào)括起來(lái)。。。hset 字典名 鍵 值 (integer) 1 > hset books golang "concurrency in go" (integer) 1 > hset books python "python cookbook" (integer) 1 > hgetall books //entries(),key 和 value 間隔出現(xiàn) 1) "java" 2) "think in java" 3) "golang" 4) "concurrency in go" 5) "python" 6) "python cookbook" > hlen books (integer) 3 > hget books java "think in java" > hset books golang "learning go programming" //因?yàn)槭歉虏僮?#xff0c;所以返回 0 (integer) 0 > hget books golang "learning go programming" > hmset books java "effective java" python "learning python" golang "modern golang programming" // 批量 set OK...? ? ? ?同字符串一樣,hash結(jié)構(gòu)中單個(gè)子key也可以計(jì)數(shù),對(duì)應(yīng)的指令 hincrby ,和incr基本一樣。
?
HSET myhash field 5 (integer) 1 HINCRBY myhash field 1 (integer) 6 HINCRBY myhash field -1 (integer) 5 HINCRBY myhash field -10 (integer) -5
?
? ? ??
? ? ? ?set(集合)
? ? ? ?Redis的集合相當(dāng)于Java中的HashSet,它內(nèi)部的鍵值對(duì)時(shí)無(wú)序且唯一的。
? ? ? ?當(dāng)集合中最后一個(gè)元素移除之后,數(shù)據(jù)結(jié)構(gòu)自動(dòng)刪除,內(nèi)存被回收。
? ? ? ?Set結(jié)構(gòu)可以存儲(chǔ)某些特殊場(chǎng)景的數(shù)據(jù),比如活動(dòng)中獎(jiǎng)用戶的ID,可以保證用一個(gè)用戶不會(huì)中獎(jiǎng)兩次。
?
> sadd books python (integer) 1 > sadd books python # 重復(fù) (integer) 0 > sadd books java golang (integer) 2 > smembers books // 注意順序,和插入的并不一致,因?yàn)?set 是無(wú)序的 1) "java" 2) "python" 3) "golang" > sismember books java // 查詢某個(gè) value 是否存在,相當(dāng)于 contains(o) (integer) 1 > sismember books rust (integer) 0 > scard books // 獲取長(zhǎng)度相當(dāng)于 count() (integer) 3 > spop books // 彈出一個(gè) "java"...? ? ? ?zset(有序列表)
? ? ? ?zset是Redis提供的最為特色的數(shù)據(jù)結(jié)構(gòu)。可以把它想象成HashMap和SortedSet的結(jié)合體,一方面它是一個(gè)set,保證了內(nèi)部value值得唯一性,另一方面它可以給每一個(gè)value都賦予一個(gè)score,代表這個(gè)value的排序權(quán)重,也就是說(shuō)根據(jù)這個(gè)score的分?jǐn)?shù)來(lái)排序。
?
? ? ? ?zset中最后一個(gè)value被移除后,數(shù)據(jù)結(jié)構(gòu)會(huì)自動(dòng)刪除,內(nèi)存會(huì)被回收。
? ? ? ?下面是一些常用的命令
> 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 排序列出,0表示第一個(gè)成員,-1表示倒數(shù)第一個(gè)成員 1) "java cookbook" 2) "java concurrency" 3) "think in java" > zrevrange books 0 -1 // 按 score 逆序列出,參數(shù)區(qū)間為排名范圍 1) "think in java" 2) "java concurrency" 3) "java cookbook" > zcard books // 統(tǒng)計(jì)這個(gè)列表中元素的個(gè)數(shù) (integer) 3 > zscore books "java concurrency" // 獲取指定 value 的 score "8.9000000000000004" // 內(nèi)部 score 使用 double 類型進(jìn)行存儲(chǔ),所以存在小數(shù)點(diǎn)精度問(wèn)題 > zrank books "java concurrency" // 排名 (integer) 1 > zrangebyscore books 0 8.91 //根據(jù)分值區(qū)間遍歷 zset 1) "java cookbook" 2) "java concurrency" > zrangebyscore books -inf 8.91 withscores // 根據(jù)分值區(qū)間 (-∞, 8.91] 遍歷 zset,同時(shí)返回分值。inf 代表 infinite,無(wú)窮大的意思。 1) "java cookbook" 2) "8.5999999999999996" 3) "java concurrency" 4) "8.9000000000000004" > zrem books "java concurrency" // 刪除 value (integer) 1 > zrange books 0 -1 1) "java cookbook" 2) "think in java"...? ? ? ?zset的內(nèi)部排序是通過(guò)【跳躍列表】的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的,這種數(shù)據(jù)結(jié)構(gòu)比較復(fù)雜,也很特殊。
? ? ? ?因?yàn)閦set要支持隨機(jī)的插入和刪除,所以不好用數(shù)組來(lái)表示。這個(gè)我們到后面再具體探討跳躍列表
?
? ? 容器型數(shù)據(jù)結(jié)構(gòu)的通用規(guī)則
? ? ? ?list/set/hash/zset 這四種數(shù)據(jù)結(jié)構(gòu)是容器型的數(shù)據(jù)結(jié)構(gòu),有如下兩條通用規(guī)則:
?
? ? ? ?1、create if not exists
? ? ? ? ? 如果容器不存在,那就創(chuàng)建一個(gè),再進(jìn)行操作。
? ? ? ?2、drop if no elements
? ? ? ? ? ?如果容器里的元素沒(méi)有了,那么立刻刪除容器,釋放內(nèi)存。
?
? ? ?過(guò)期時(shí)間
? ? ? ?Redis 所有的數(shù)據(jù)結(jié)構(gòu)都可以設(shè)置過(guò)期時(shí)間,時(shí)間到了,Redis會(huì)自動(dòng)刪除相應(yīng)的對(duì)象。需要注意的是過(guò)期是一對(duì)象為單位,比如一個(gè)hash結(jié)構(gòu)的過(guò)期是整個(gè)hash對(duì)象的過(guò)期,而不是其中的某個(gè)鍵值。
? ? ? ?還有一個(gè)需要注意的是如果一個(gè)字符已經(jīng)設(shè)置了過(guò)期時(shí)間,但是你調(diào)用了set方法修改他,它的過(guò)期時(shí)間就會(huì)消失。
?
?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/WeirdRomance/p/9511495.html
總結(jié)
以上是生活随笔為你收集整理的Redis 之(二) Redis的基本数据结构以及一些常用的操作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: P1131 [ZJOI2007]时态同步
- 下一篇: Git学习记录 力做全网最强入门教程