Redis存儲結構
1 簡介
從Redis系列(一):Redis數據結構 可以瞭解到,Redis爲用戶提供了多種數據結構及相應API,用戶可隨心使用,本文將具體介紹這些數據結構的具體實現。
2 Redis存儲結構
Redis主要實現了6中存儲結構,分別爲:整數集合;字典、字符串;鏈表、壓縮列表、跳躍表。
2.1 SDS
SDS是Simple Dynamic String
的首字母縮寫,是對C語言中的字符串的封裝,其主要數據結構定義如下:
struct sdshdr {
// 表示已分配使用的字節數
// buf數組已使用的長度
int len;
// 表示SDS未使用的字節數;
int free;
// 字節數組保存二進制數據
// 遵循C語言規則,以'\0'結尾,但不計算在len屬性
char buf[];
};
在Redis系列(一):Redis數據結構 已經講過,Redis所有的鍵都是字符串類型,也就是說所有的鍵都是SDS對象,舉例來說:
set msg “hello world”
該命令創建了一個鍵值對,且鍵和值都是字符串類型,鍵是一個保存着msg的SDS,值是保存着 hello world的SDS;
SDS是對在C字符串的基礎上進行的封裝,具有以下優勢:
常數複雜度獲取字符串長度
:C語言獲取字符串長度需要進行一次便利,SDS只需要通過獲取len屬性即可達到目的;減少修改字符串時帶來的內存重分配
:C字符串拼接或截斷操作都需要重新分配內存空間,而SDS在分配內存空間時,預留了free長度的空閒空間,能夠有效減少內存衝分配次數;二進制安全
:C字符串只能保存文本數據,而SDS能保存包括圖片、音頻、視頻等二進制數據,其原因是SDS是通過len屬性而不是’\0’來判斷字符串的結尾。杜絕緩衝區溢出
:C字符串拼接需要手動分配內存空間,以容納拼接後的字符串,如果忘記該操作就會導致緩衝區溢出,SDS已經封裝好了字符串拼接操作,可完全避免緩衝區溢出問題。2.2 鏈表
鏈表是一種常用的數據結構,其基本特性大家都應該很瞭解,如高效的節點重排能力、順序性節點訪問、靈活的節點增刪等。Redis使用的C語言並沒有內置這種數據結構,因此Redis自己實現了鏈表結構。
鏈表節點listNode主要包含三個屬性:前置節點 *prev、後置節點 *next及節點值 *value,並且通過list結構來持有鏈表:
/**
* 鏈表節點數據結構
**/
typedef struct listNode{
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
/**
* 通過list持有鏈表
**/
typedef struct list{
// 頭節點
listNode *head;
// 尾節點
listNode *tail;
// 鏈表節點數量
unsigned int len;
// 節點複製函數
void *(*dup)(void *ptr);
// 節點釋放函數
void *(*free)(void *ptr);
// 節點值對比函數
void *(*match)(void *ptr);
} list;
Redis鏈表具有以下特徵:
- 雙端:list結構持有head和tail指針,獲取表頭或表尾節點時間複雜度爲O(1);
- 雙向:鏈表每個節點都帶有prev和next指針,獲取前置節點或後置節點時間複雜度爲O(1);
- 無環:頭節點的prev和尾節點的next都爲NULL;
長度計數器:list通過len屬性記錄鏈表節點數量;
2.3 整數集合
整數集合是集合鍵的底層實現之一,當一個集合只包含整數且數量不多時,Redis就會使用整數集合intset作爲底層實現。
intset可以保存int16_t, int32_t, int64_t的整數值,並且可以保證不重複。
typedef struct intset {
// 編碼方式
uint32_t encoding;
// 集合包含的元素數量
uint32_t length;
// 升序保存着所有元素
int8_t contents[];
} intset;
contents屬性聲明爲int8_t類型,但實際上元素的類型取決於encoding屬性的值:
- 如果encoding爲INTSET_ENC_INT16,那麼contents所有元素都是int16_t類型整數值;
- 如果encoding爲INTSET_ENC_INT32,那麼contents所有元素都是int32_t類型整數值;
如果encoding爲INTSET_ENC_INT64,那麼contents所有元素都是int6_4t類型整數值;
升級: 當將一個新元素添加到整數集合,且其長度比集合中任何元素類型都長時,整數集合需要先進行升級,升級分爲三步:
1)根據新元素類型,分配內存空間,包括已有元素和新元素;
2)將已有元素轉換成新元素類型,並放置到新的內存空間;
3)將新元素添加到內存空間;
整個升級過程保證所有元素升序排序。對於具體的內存分配過程,請大家參閱《Redis設計與實現》。
需要說明的是 整數集合不支持降級,因此一旦對數組進行了升級,編碼就會一直保持升級後的狀態。