Redis 5種數據結構及對應使用場景

本文案例收錄在 https://github.com/chengxy-nds/Springboot-Notebook

也當過面試官,面試過不少應聘者,因爲是我自己招人自己用,所以我不會看應聘者造火箭的技術有多牛比,只看擰螺絲的手藝瓷不瓷實。畢竟以後是一個整體,拖了大家後腿團隊都很難受。面試的題目一般也不會太難,就像問Redis,我只是想確認他真正用過就夠了。Redis 5種基礎數據結構和簡單操作要知道,最基本的要求,如果這個時候他會說出每種數據結構大致的應用場景,那麼這一定是加分的,起碼要比那些只會說出幾種數據結構後,在那乾瞪眼等我問下一個問題的強很多,千萬別冷場。

有想交流技術或面試經驗的可以加我VX:xinzhifu521,一定知無不言,好了就聊這麼多進入正題!


Redis基礎數據結構有哪些?

一、String(字符串)

在任何一種編程語言裏,字符串String都是最基礎的數據結構, 那你有想過Redis中存儲一個字符串都進行了哪些操作嘛?

RedisString是可以修改的,稱爲動態字符串(Simple Dynamic String 簡稱 SDS)(快拿小本本記名詞,要考的),說是字符串但它的內部結構更像是一個 ArrayList,內部維護着一個字節數組,並且在其內部預分配了一定的空間,以減少內存的頻繁分配。

Redis的內存分配機制是這樣:

  • 當字符串的長度小於 1MB時,每次擴容都是加倍現有的空間。

  • 如果字符串長度超過 1MB時,每次擴容時只會擴展 1MB 的空間。

這樣既保證了內存空間夠用,還不至於造成內存的浪費,字符串最大長度爲 512MB.
在這裏插入圖片描述

以上圖片源自網絡,如有侵權聯繫刪除

上圖就是字符串的基本結構,其中 content 裏面保存的是字符串內容,0x\0作爲結束字符不會被計算len中。

分析一下字符串的數據結構

struct SDS{
  T capacity;       //數組容量
  T len;            //實際長度
  byte flages;  //標誌位,低三位表示類型
  byte[] content;   //數組內容
}

capacitylen兩個屬性都是泛型,爲什麼不直接用int類型?因爲Redis內部有很多優化方案,爲更合理的使用內存,不同長度的字符串採用不同的數據類型表示,且在創建字符串的時候 len 會和 capacity 一樣大,不產生冗餘的空間,所以String值可以是字符串、數字(整數、浮點數) 或者 二進制。

1、應用場景:

存儲key-value鍵值對,這個比較簡單不細說了

2、字符串(String)常用的命令:

set   [key]  [value]   給指定key設置值(set 可覆蓋老的值)

get  [key]   獲取指定key 的值

del  [key]   刪除指定key

exists  [key]  判斷是否存在指定key

mset  [key1]  [value1]  [key2]  [value2] ...... 批量存鍵值對

mget  [key1]  [key2] ......   批量取key

expire [key]  [time]    給指定key 設置過期時間  單位秒

setex    [key]  [time]  [value]  等價於 set + expire 命令組合

setnx  [key]  [value]   如果key不存在則set 創建,否則返回0

incr   [key]           如果value爲整數 可用 incr命令每次自增1

incrby  [key] [number]  使用incrby命令對整數值 進行增加 number

二、list(列表)

Redis中的listJava中的LinkedList很像,底層都是一種鏈表結構, list的插入和刪除操作非常快,時間複雜度爲 0(1),不像數組結構插入、刪除操作需要移動數據。

像歸像,但是redis中的list底層可不是一個雙向鏈表那麼簡單。

當數據量較少的時候它的底層存儲結構爲一塊連續內存,稱之爲ziplist(壓縮列表),它將所有的元素緊挨着一起存儲,分配的是一塊連續的內存;當數據量較多的時候將會變成quicklist(快速鏈表)結構。

可單純的鏈表也是有缺陷的,鏈表的前後指針 prevnext 會佔用較多的內存,會比較浪費空間,而且會加重內存的碎片化。在redis 3.2之後就都改用ziplist+鏈表的混合結構,稱之爲 quicklist(快速鏈表)

下面具體介紹下兩種鏈表

ziplist(壓縮列表)

先看一下ziplist的數據結構,

struct ziplist<T>{
    int32 zlbytes;            //壓縮列表佔用字節數
    int32 zltail_offset;    //最後一個元素距離起始位置的偏移量,用於快速定位到最後一個節點
    int16 zllength;            //元素個數
    T[] entries;            //元素內容
    int8 zlend;                //結束位 0xFF
}

int32 zlbytes: 壓縮列表佔用字節數
int32 zltail_offset: 最後一個元素距離起始位置的偏移量,用於快速定位到最後一個節點
int16 zllength:元素個數
T[] entries:元素內容
int8 zlend:結束位 0xFF

壓縮列表爲了支持雙向遍歷,所以纔會有 ztail_offset 這個字段,用來快速定位到最後一
個元素,然後倒着遍歷
在這裏插入圖片描述

以上圖片源自網絡,如有侵權聯繫刪除

entry的數據結構:

struct entry{
    int<var> prevlen;            //前一個 entry 的長度
    int<var> encoding;            //元素類型編碼
    optional byte[] content;    //元素內容
}

entry它的 prevlen 字段表示前一個 entry 的字節長度,當壓縮列表倒着遍歷時,需要通過這
個字段來快速定位到下一個元素的位置。

1、應用場景:

由於list它是一個按照插入順序排序的列表,所以應用場景相對還較多的,例如:

  • 消息隊列:lpoprpush(或者反過來,lpushrpop)能實現隊列的功能

  • 朋友圈的點贊列表、評論列表、排行榜:lpush命令和lrange命令能實現最新列表的功能,每次通過lpush命令往列表裏插入新的元素,然後通過lrange命令讀取最新的元素列表。

2、list操作的常用命名:

rpush  [key] [value1] [value2] ......    鏈表右側插入

rpop    [key]  移除右側列表頭元素,並返回該元素

lpop   [key]    移除左側列表頭元素,並返回該元素

llen  [key]     返回該列表的元素個數

lrem [key] [count] [value]  刪除列表中與value相等的元素,count是刪除的個數。 count>0 表示從左側開始查找,刪除count個元素,count<0 表示從右側開始查找,刪除count個相同元素,count=0 表示刪除全部相同的元素

(PS:   index 代表元素下標,index 可以爲負數, index= 表示倒數第一個元素,同理 index=-2 表示倒數第二 個元素。)

lindex [key] [index]  獲取list指定下標的元素 (需要遍歷,時間複雜度爲O(n))

lrange [key]  [start_index] [end_index]   獲取list 區間內的所有元素 (時間複雜度爲 O(n))

ltrim  [key]  [start_index] [end_index]   保留區間內的元素,其他元素刪除(時間複雜度爲 O(n))

三、hash (字典)

Redis 中的 Hash和 Java的 HashMap 更加相似,都是數組+鏈表的結構,當發生 hash 碰撞時將會把元素追加到鏈表上,值得注意的是在 RedisHashvalue 只能是字符串.

hset books java "Effective java" (integer) 1
hset books golang "concurrency in go" (integer) 1
hget books java "Effective java"
hset user age 17 (integer) 1
hincrby user age 1	#單個 key 可以進行計數 和 incr 命令基本一致 (integer) 18

HashString都可以用來存儲用戶信息 ,但不同的是Hash可以對用戶信息的每個字段單獨存儲;String存的是用戶全部信息經過序列化後的字符串,如果想要修改某個用戶字段必須將用戶信息字符串全部查詢出來,解析成相應的用戶信息對象,修改完後在序列化成字符串存入。而 hash可以只對某個字段修改,從而節約網絡流量,不過hash內存佔用要大於 String,這是 hash 的缺點。

1、應用場景:

 

  • 購物車:hset [key] [field] [value] 命令, 可以實現以用戶Id商品Idfield,商品數量爲value,恰好構成了購物車的3個要素。
  • 存儲對象:hash類型的(key, field, value)的結構與對象的(對象id, 屬性, 值)的結構相似,也可以用來存儲對象。

2、hash常用的操作命令:

hset  [key]  [field] [value]    新建字段信息

hget  [key]  [field]    獲取字段信息

hdel [key] [field]  刪除字段

hlen  [key]   保存的字段個數

hgetall  [key]  獲取指定key 字典裏的所有字段和值 (字段信息過多,會導致慢查詢 慎用:親身經歷 曾經用過這個這個指令導致線上服務故障)

hmset  [key]  [field1] [value1] [field2] [value2] ......   批量創建

hincr  [key] [field]   對字段值自增

hincrby [key] [field] [number] 對字段值增加number

四、set(集合)

Redis 中的 setJava中的HashSet 有些類似,它內部的鍵值對是無序的、唯一 的。它的內部實現相當於一個特殊的字典,字典中所有的value都是一個值 NULL。當集合中最後一個元素被移除之後,數據結構被自動刪除,內存被回收。

1、應用場景:

  • 好友、關注、粉絲、感興趣的人集合:
    1. sinter命令可以獲得A和B兩個用戶的共同好友;
    2. sismember命令可以判斷A是否是B的好友;
    3. scard命令可以獲取好友數量;
    4. 關注時,smove命令可以將B從A的粉絲集合轉移到A的好友集合
  • 首頁展示隨機:美團首頁有很多推薦商家,但是並不能全部展示,set類型適合存放所有需要展示的內容,而srandmember命令則可以從中隨機獲取幾個。
  • 存儲某活動中中獎的用戶ID ,因爲有去重功能,可以保證同一個用戶不會中獎兩次。

2、set的常用命令:

sadd  [key]  [value]  向指定key的set中添加元素

smembers [key]    獲取指定key 集合中的所有元素

sismember [key] [value]   判斷集合中是否存在某個value

scard [key]    獲取集合的長度

spop  [key]   彈出一個元素

srem [key] [value]  刪除指定元素

五、zset(有序集合)

zset也叫SortedSet一方面它是個 set ,保證了內部 value 的唯一性,另方面它可以給每個 value 賦予一個score,代表這個value的排序權重。它的內部實現用的是一種叫作“跳躍列表”的數據結構。

1、應用場景:

zset 可以用做排行榜,但是和list不同的是zset它能夠實現動態的排序,例如: 可以用來存儲粉絲列表,value 值是粉絲的用戶 ID,score 是關注時間,我們可以對粉絲列表按關注時間進行排序。

zset 還可以用來存儲學生的成績, value 值是學生的 ID, score 是他的考試成績。 我們對成績按分數進行排序就可以得到他的名次。

2、zset有序集合的常用操作命令:

zadd [key] [score] [value] 向指定key的集合中增加元素

zrange [key] [start_index] [end_index] 獲取下標範圍內的元素列表,按score 排序輸出

zrevrange [key] [start_index] [end_index]  獲取範圍內的元素列表 ,按score排序 逆序輸出

zcard [key]  獲取集合列表的元素個數

zrank [key] [value]  獲取元素再集合中的排名

zrangebyscore [key] [score1] [score2]  輸出score範圍內的元素列表

zrem [key] [value]  刪除元素

zscore [key] [value] 獲取元素的score


總結

本文很多概念都一帶而過了,只是給大家粗略的講述一下Redis五種基礎數據結構和應用場景,旨在給小夥伴們一個面試備題的方向,後續會持續輸出Redis方面的文章,歡迎關注,咱們一起學習拿offer。

本文案例收錄在 https://github.com/chengxy-nds/Springboot-Notebook

也當過面試官,面試過不少應聘者,因爲是我自己招人自己用,所以我不會看應聘者造火箭的技術有多牛比,只看擰螺絲的手藝瓷不瓷實。畢竟以後是一個整體,拖了大家後腿團隊都很難受。面試的題目一般也不會太難,就像問Redis,我只是想確認他真正用過就夠了。Redis 5種基礎數據結構和簡單操作要知道,最基本的要求,如果這個時候他會說出每種數據結構大致的應用場景,那麼這一定是加分的,起碼要比那些只會說出幾種數據結構後,在那乾瞪眼等我問下一個問題的強很多,千萬別冷場。

有想交流技術或面試經驗的可以加我VX:xinzhifu521,一定知無不言,好了就聊這麼多進入正題!


Redis基礎數據結構有哪些?

一、String(字符串)

在任何一種編程語言裏,字符串String都是最基礎的數據結構, 那你有想過Redis中存儲一個字符串都進行了哪些操作嘛?

RedisString是可以修改的,稱爲動態字符串(Simple Dynamic String 簡稱 SDS)(快拿小本本記名詞,要考的),說是字符串但它的內部結構更像是一個 ArrayList,內部維護着一個字節數組,並且在其內部預分配了一定的空間,以減少內存的頻繁分配。

Redis的內存分配機制是這樣:

  • 當字符串的長度小於 1MB時,每次擴容都是加倍現有的空間。

  • 如果字符串長度超過 1MB時,每次擴容時只會擴展 1MB 的空間。

這樣既保證了內存空間夠用,還不至於造成內存的浪費,字符串最大長度爲 512MB.
在這裏插入圖片描述

以上圖片源自網絡,如有侵權聯繫刪除

上圖就是字符串的基本結構,其中 content 裏面保存的是字符串內容,0x\0作爲結束字符不會被計算len中。

分析一下字符串的數據結構

struct SDS{
  T capacity;       //數組容量
  T len;            //實際長度
  byte flages;  //標誌位,低三位表示類型
  byte[] content;   //數組內容
}

capacitylen兩個屬性都是泛型,爲什麼不直接用int類型?因爲Redis內部有很多優化方案,爲更合理的使用內存,不同長度的字符串採用不同的數據類型表示,且在創建字符串的時候 len 會和 capacity 一樣大,不產生冗餘的空間,所以String值可以是字符串、數字(整數、浮點數) 或者 二進制。

1、應用場景:

存儲key-value鍵值對,這個比較簡單不細說了

2、字符串(String)常用的命令:

set   [key]  [value]   給指定key設置值(set 可覆蓋老的值)

get  [key]   獲取指定key 的值

del  [key]   刪除指定key

exists  [key]  判斷是否存在指定key

mset  [key1]  [value1]  [key2]  [value2] ...... 批量存鍵值對

mget  [key1]  [key2] ......   批量取key

expire [key]  [time]    給指定key 設置過期時間  單位秒

setex    [key]  [time]  [value]  等價於 set + expire 命令組合

setnx  [key]  [value]   如果key不存在則set 創建,否則返回0

incr   [key]           如果value爲整數 可用 incr命令每次自增1

incrby  [key] [number]  使用incrby命令對整數值 進行增加 number

二、list(列表)

Redis中的listJava中的LinkedList很像,底層都是一種鏈表結構, list的插入和刪除操作非常快,時間複雜度爲 0(1),不像數組結構插入、刪除操作需要移動數據。

像歸像,但是redis中的list底層可不是一個雙向鏈表那麼簡單。

當數據量較少的時候它的底層存儲結構爲一塊連續內存,稱之爲ziplist(壓縮列表),它將所有的元素緊挨着一起存儲,分配的是一塊連續的內存;當數據量較多的時候將會變成quicklist(快速鏈表)結構。

可單純的鏈表也是有缺陷的,鏈表的前後指針 prevnext 會佔用較多的內存,會比較浪費空間,而且會加重內存的碎片化。在redis 3.2之後就都改用ziplist+鏈表的混合結構,稱之爲 quicklist(快速鏈表)

下面具體介紹下兩種鏈表

ziplist(壓縮列表)

先看一下ziplist的數據結構,

struct ziplist<T>{
    int32 zlbytes;            //壓縮列表佔用字節數
    int32 zltail_offset;    //最後一個元素距離起始位置的偏移量,用於快速定位到最後一個節點
    int16 zllength;            //元素個數
    T[] entries;            //元素內容
    int8 zlend;                //結束位 0xFF
}

int32 zlbytes: 壓縮列表佔用字節數
int32 zltail_offset: 最後一個元素距離起始位置的偏移量,用於快速定位到最後一個節點
int16 zllength:元素個數
T[] entries:元素內容
int8 zlend:結束位 0xFF

壓縮列表爲了支持雙向遍歷,所以纔會有 ztail_offset 這個字段,用來快速定位到最後一
個元素,然後倒着遍歷
在這裏插入圖片描述

以上圖片源自網絡,如有侵權聯繫刪除

entry的數據結構:

struct entry{
    int<var> prevlen;            //前一個 entry 的長度
    int<var> encoding;            //元素類型編碼
    optional byte[] content;    //元素內容
}

entry它的 prevlen 字段表示前一個 entry 的字節長度,當壓縮列表倒着遍歷時,需要通過這
個字段來快速定位到下一個元素的位置。

1、應用場景:

由於list它是一個按照插入順序排序的列表,所以應用場景相對還較多的,例如:

  • 消息隊列:lpoprpush(或者反過來,lpushrpop)能實現隊列的功能

  • 朋友圈的點贊列表、評論列表、排行榜:lpush命令和lrange命令能實現最新列表的功能,每次通過lpush命令往列表裏插入新的元素,然後通過lrange命令讀取最新的元素列表。

2、list操作的常用命名:

rpush  [key] [value1] [value2] ......    鏈表右側插入

rpop    [key]  移除右側列表頭元素,並返回該元素

lpop   [key]    移除左側列表頭元素,並返回該元素

llen  [key]     返回該列表的元素個數

lrem [key] [count] [value]  刪除列表中與value相等的元素,count是刪除的個數。 count>0 表示從左側開始查找,刪除count個元素,count<0 表示從右側開始查找,刪除count個相同元素,count=0 表示刪除全部相同的元素

(PS:   index 代表元素下標,index 可以爲負數, index= 表示倒數第一個元素,同理 index=-2 表示倒數第二 個元素。)

lindex [key] [index]  獲取list指定下標的元素 (需要遍歷,時間複雜度爲O(n))

lrange [key]  [start_index] [end_index]   獲取list 區間內的所有元素 (時間複雜度爲 O(n))

ltrim  [key]  [start_index] [end_index]   保留區間內的元素,其他元素刪除(時間複雜度爲 O(n))

三、hash (字典)

Redis 中的 Hash和 Java的 HashMap 更加相似,都是數組+鏈表的結構,當發生 hash 碰撞時將會把元素追加到鏈表上,值得注意的是在 RedisHashvalue 只能是字符串.

hset books java "Effective java" (integer) 1
hset books golang "concurrency in go" (integer) 1
hget books java "Effective java"
hset user age 17 (integer) 1
hincrby user age 1	#單個 key 可以進行計數 和 incr 命令基本一致 (integer) 18

HashString都可以用來存儲用戶信息 ,但不同的是Hash可以對用戶信息的每個字段單獨存儲;String存的是用戶全部信息經過序列化後的字符串,如果想要修改某個用戶字段必須將用戶信息字符串全部查詢出來,解析成相應的用戶信息對象,修改完後在序列化成字符串存入。而 hash可以只對某個字段修改,從而節約網絡流量,不過hash內存佔用要大於 String,這是 hash 的缺點。

1、應用場景:

 

  • 購物車:hset [key] [field] [value] 命令, 可以實現以用戶Id商品Idfield,商品數量爲value,恰好構成了購物車的3個要素。
  • 存儲對象:hash類型的(key, field, value)的結構與對象的(對象id, 屬性, 值)的結構相似,也可以用來存儲對象。

2、hash常用的操作命令:

hset  [key]  [field] [value]    新建字段信息

hget  [key]  [field]    獲取字段信息

hdel [key] [field]  刪除字段

hlen  [key]   保存的字段個數

hgetall  [key]  獲取指定key 字典裏的所有字段和值 (字段信息過多,會導致慢查詢 慎用:親身經歷 曾經用過這個這個指令導致線上服務故障)

hmset  [key]  [field1] [value1] [field2] [value2] ......   批量創建

hincr  [key] [field]   對字段值自增

hincrby [key] [field] [number] 對字段值增加number

四、set(集合)

Redis 中的 setJava中的HashSet 有些類似,它內部的鍵值對是無序的、唯一 的。它的內部實現相當於一個特殊的字典,字典中所有的value都是一個值 NULL。當集合中最後一個元素被移除之後,數據結構被自動刪除,內存被回收。

1、應用場景:

  • 好友、關注、粉絲、感興趣的人集合:
    1. sinter命令可以獲得A和B兩個用戶的共同好友;
    2. sismember命令可以判斷A是否是B的好友;
    3. scard命令可以獲取好友數量;
    4. 關注時,smove命令可以將B從A的粉絲集合轉移到A的好友集合
  • 首頁展示隨機:美團首頁有很多推薦商家,但是並不能全部展示,set類型適合存放所有需要展示的內容,而srandmember命令則可以從中隨機獲取幾個。
  • 存儲某活動中中獎的用戶ID ,因爲有去重功能,可以保證同一個用戶不會中獎兩次。

2、set的常用命令:

sadd  [key]  [value]  向指定key的set中添加元素

smembers [key]    獲取指定key 集合中的所有元素

sismember [key] [value]   判斷集合中是否存在某個value

scard [key]    獲取集合的長度

spop  [key]   彈出一個元素

srem [key] [value]  刪除指定元素

五、zset(有序集合)

zset也叫SortedSet一方面它是個 set ,保證了內部 value 的唯一性,另方面它可以給每個 value 賦予一個score,代表這個value的排序權重。它的內部實現用的是一種叫作“跳躍列表”的數據結構。

1、應用場景:

zset 可以用做排行榜,但是和list不同的是zset它能夠實現動態的排序,例如: 可以用來存儲粉絲列表,value 值是粉絲的用戶 ID,score 是關注時間,我們可以對粉絲列表按關注時間進行排序。

zset 還可以用來存儲學生的成績, value 值是學生的 ID, score 是他的考試成績。 我們對成績按分數進行排序就可以得到他的名次。

2、zset有序集合的常用操作命令:

zadd [key] [score] [value] 向指定key的集合中增加元素

zrange [key] [start_index] [end_index] 獲取下標範圍內的元素列表,按score 排序輸出

zrevrange [key] [start_index] [end_index]  獲取範圍內的元素列表 ,按score排序 逆序輸出

zcard [key]  獲取集合列表的元素個數

zrank [key] [value]  獲取元素再集合中的排名

zrangebyscore [key] [score1] [score2]  輸出score範圍內的元素列表

zrem [key] [value]  刪除元素

zscore [key] [value] 獲取元素的score


總結

本文很多概念都一帶而過了,只是給大家粗略的講述一下Redis五種基礎數據結構和應用場景,旨在給小夥伴們一個面試備題的方向,後續會持續輸出Redis方面的文章,歡迎關注,咱們一起學習拿offer。


整理了幾百本各類技術電子書,送給小夥伴們。關注公號回覆【666】自行領取。和一些小夥伴們建了一個技術交流羣,一起探討技術、分享技術資料,旨在共同學習進步,如果感興趣就加入我們吧!

在這裏插入圖片描述

電子書地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章