剛寫了redis主要的數據結構:
動態字符串、雙端鏈表、字典、壓縮列表、整數集合、跳表等
redis肯定不能直接使用這些數據結構來實現數據庫,它用這些數據庫建立了一個對象系統,包含:
字符串對象、列表對象、哈希對象、集合對象、有序集合對象
我們可以針對不同的使用場景,為對象設置多種分不同的數據結構實現,從而優化對象在不同場景下的效率。
鍵值對
對于redis的鍵值對來說:key只有字符串類型,而v可以是各種類型,
我們習慣把“這個鍵所對應的值是一個列表”表達為這是一個“列表鍵。
TYPE?命令的實現方式也與此類似, 當我們對一個數據庫鍵執行?TYPE?命令時, 命令返回的結果為數據庫鍵對應的值對象的類型, 而不是鍵對象的類型:
# 鍵為字符串對象,值為列表對象redis> RPUSH numbers 1 3 5
(integer) 6redis> TYPE numbers
list
對象
我們看一下redis對象的組成:
typedef struct redisObject {// 類型unsigned type:4;// 編碼unsigned encoding:4;// 指向底層實現數據結構的指針void *ptr;// ...
} robj;
通過?encoding
?屬性來設定對象所使用的編碼, 而不是為特定類型的對象關聯一種固定的編碼, 極大地提升了 Redis 的靈活性和效率, 因為 Redis 可以根據不同的使用場景來為一個對象設置不同的編碼, 從而優化對象在某一場景下的效率。
字符串對象
字符串對象的編碼可以是?int
?、?raw
?或者?embstr
?。
如果一個字符串對象保存的是整數值, 并且這個整數值可以用?long
?類型來表示, 那么字符串對象會將整數值保存在字符串對象結構的?ptr
屬性里面(將?void*
?轉換成?long
?), 并將字符串對象的編碼設置為?int
?。
如果字符串對象保存的是一個字符串值, 并且這個字符串值的長度大于?39
?字節, 那么字符串對象將使用一個簡單動態字符串(SDS)來保存這個字符串值, 并將對象的編碼設置為?raw
?。
如果字符串對象保存的是一個字符串值, 并且這個字符串值的長度小于等于?39
?字節, 那么字符串對象將使用?embstr
?編碼的方式來保存這個字符串值。
embstr
?編碼是專門用于保存短字符串的一種優化編碼方式, 這種編碼和?raw
?編碼一樣, 都使用?redisObject
?結構和?sdshdr
?結構來表示字符串對象,但?raw
?編碼會調用兩次內存分配函數來分別創建?redisObject
?結構和?sdshdr
?結構,而?embstr
?編碼則通過調用一次內存分配函數來分配一塊連續的空間, 空間中依次包含?redisObject
?和?sdshdr
?兩個結構。
?embstr
?編碼有以下好處:
embstr
?編碼創建刪除字符串對象只需操作一次內存- 因為數據都保存在一塊連續的內存, 所以這種編碼的字符串對象比?
raw
?編碼字符串對象能更好地利用緩存帶來的優勢。
列表對象
列表對象的編碼可以是?ziplist
?或者?linkedlist
?。
當列表對象可以同時滿足以下兩個條件時, 列表對象使用?ziplist
?編碼:
- 列表對象保存的所有字符串元素的長度都小于?
64
?字節; - 列表對象保存的元素數量小于?
512
?個;
不能滿足這兩個條件的列表對象需要使用?linkedlist
?編碼。
哈希對象
哈希對象的編碼可以是?ziplist
?或者?hashtable
?。
當哈希對象可以同時滿足以下兩個條件時, 哈希對象使用?ziplist
?編碼:
- 哈希對象保存的所有鍵值對的鍵和值的字符串長度都小于?
64
?字節; - 哈希對象保存的鍵值對數量小于?
512
?個;
不能滿足這兩個條件的哈希對象需要使用?hashtable
?編碼。
集合對象
集合對象的編碼可以是?intset
?或者?hashtable
?。
當集合對象可以同時滿足以下兩個條件時, 對象使用?intset
?編碼:
- 集合對象保存的所有元素都是整數值;
- 集合對象保存的元素數量不超過?
512
?個;
不能滿足這兩個條件的集合對象需要使用?hashtable
?編碼。
有序集合對象
有序集合的編碼可以是?ziplist
?或者?skiplist
?。
當有序集合對象可以同時滿足以下兩個條件時, 對象使用?ziplist
?編碼:
- 有序集合保存的元素數量小于?
128
?個; - 有序集合保存的所有元素成員的長度都小于?
64
?字節;
不能滿足以上兩個條件的有序集合對象將使用?skiplist
?編碼。
這里多說兩句,各個語言的對象其實都差不多,底層實現也就那幾個,比如java中的容器,c++的STL。java的hashset就是一個哈希而已,hashmap就是k帶了一個v,而”有序的“Treemap使用了紅黑樹這種有平衡性的搜索二叉樹。
redis的有序集合并沒有再采取hash+紅黑樹的操作,而是把平衡樹換成了跳表,實際上性能真的沒差多少,甚至有時比紅黑樹有優勢,比如跳表的性能較為平均,紅黑樹攢了很多次不平衡要調整可能會帶來資源需求的一個高峰,再加上跳表實現簡單的優點,紅黑樹真的沒什么優勢。
并且就算是真的想用一種帶平衡性的搜索樹,現在競賽也是用的華人之光發明的SB樹。
有序集合的優點就是它的有序操作,比如拿最大最小值,紅黑樹時間o(logN),而哈希表只能一個一個遍歷。缺點在于插入一個值的時間也是o(logN),跳表也是。而哈希表插入數是o(1).
要了解底層和這些優缺點