根據書籍《redis設計與實現》總結。
一、簡單動態字符串
在redis數據庫裏面,包含字符串值的鍵值對在底層都是由SDS實現的。
1、SDS數據結構
struct sdshdr {
//記錄buf數組中已使用的數量
//等於SDS所保存字符串的長度
int len;
//記錄buf數組中未使用字節的數量
int free;
//字節數組,用於保存字符串
char buf[];
}
特點:
(1)、常數複雜度獲取字符串長度
(2)、杜絕緩衝區溢出
當SDS API需要對SDS進行修改時,API會先檢查SDS的空間是否滿足修改所需的要求,如果不滿足的話,API會自動將SDS的空間擴展至執行修改所需要的大小,然後在執行實際的修改操作,所以使用SDS既不需要手動修改SDS的空間大小,也不會出現前面所說的緩衝區溢出問題。
3、減少修改字符串是帶來的內存重分配次數
3.1 空間預分配
空間預分配用於優化SDS的字符串增長操作:當SDS的API對一個SDS進行修改,並且需要對SDS進行空間擴展的時候,程序不僅會爲SDS分配修改所必須要的空間,還會爲SDS分配額外的未使用空間。
3.2 惰性預分配
惰性空間釋放用於優化SDS的字符串縮短操作:當SDS的API需要縮短SDS保存的字符串時,程序並不立即使用內存重分配來回收縮短後多出來的字節,而是使用free屬性將這些字節的數量記錄下來,並等待將來使用。
4、二進制安全
SDS API都會以處理二進制的方式來處理SDS存放在buf數組裏面的數據,程序不會對其中的數據做任何限制、過濾、或者假設,數據寫入時是什麼樣的,它被讀取時就是什麼樣。
5、兼容部分C字符串函數
二、鏈表
鏈表節點:
typedef struct listNode {
struct listNode *prev; //前置節點
struct listNode *next; //後置節點
void *value; //節點的值
} listNode;
鏈表:
/* 雙向鏈表的定義 */
typedef struct list {
listNode *head; //表頭節點
listNode *tail; //表尾節點
/* 定義三個函數指針 */
void *(*dup)(void *ptr); // 節點值複製函數
void (*free)(void *ptr); // 節點值釋放函數
int (*match)(void *ptr, void *key); // 節點值對比函數
unsigned long len; //鏈表所包含的節點數量
} list;
三、字典
字典,又稱爲符號表、關聯數組或映射,是一種用於保存鍵值對的抽象數據結構。
Redis數據庫就是字典來作爲底層實現的,對數據庫的增、刪、改、查操作也是構建對字典的操作之上的。
字典還是哈希鍵的底層實現之一,當一個哈希鍵包含的鍵值對比較多,又或者鍵值對中的元素都是比較城的字符串時,Redis就會使用字典作爲哈希鍵的底層實現。
1、字典的結構
Redis的字典使用哈希表作爲底層實現,一個哈希表裏面可以有多個哈希表節點,而每個哈希表節點就保存了字典中的一個鍵值對。
/* 哈希表結構 */
typedef struct dictht {
// 散列數組。
dictEntry **table;
// 散列數組的長度
unsigned long size;
// 哈希表大小掩碼,用於計算索引值,等於size減1
unsigned long sizemask;
// 散列數組中已經被使用的節點數量
unsigned long used;
} dictht;
/* 哈希表節點*/
typedef struct dictEntry {
// 關鍵字key定義
void *key;
// 值value定義,只能存放一個被選中的成員
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
// 指向下一個鍵值對節點,形成鏈表
struct dictEntry *next;
} dictEntry;
/* 字典的主操作類,對dictht結構再次包裝 */
typedef struct dict {
// 字典類型
dictType *type;
// 私有數據
void *privdata;
// 一個字典中有兩個哈希表
dictht ht[2];
// 數據動態遷移的下標位置
long rehashidx;
// 當前正在使用的迭代器的數量
int iterators;
} dict;
/* 定義了字典操作的公共方法 */
typedef struct dictType {
/* hash方法,根據關鍵字計算哈希值 */
unsigned int (*hashFunction)(const void *key);
/* 複製key */
void *(*keyDup)(void *privdata, const void *key);
/* 複製value */
void *(*valDup)(void *privdata, const void *obj);
/* 關鍵字比較方法 */
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
/* 銷燬key */
void (*keyDestructor)(void *privdata, void *key);
/* 銷燬value */
void (*valDestructor)(void *privdata, void *obj);
} dictType;
2、字典的操作
2.1 哈希算法
當要將一個新的鍵值對添加到字典裏面時,程序需要先根據鍵值對的鍵計算出哈希值和索引值,然後再根據索引值,將包含新鍵值對的哈希表節點放到哈希表數組的指定索引上面。
2.2 解決鍵衝突
Redis的哈希表使用鏈地址法來解決鍵衝突,每個哈希表節點都有一個next指針,多個哈希表節點可以用next指針構成一個單向鏈表,被分配到同一個索引上的多個節點可以用這個單向鏈表連接起來。
2.3 rehash和漸進式rehash
《redis設計與實現》P29
四、跳躍表
redis的跳躍表結構設計的真心沒看懂。《redis設計與實現》P38
五、整數集合
1、整數集合的數據結構
/* 整數集合結構體 */
typedef struct intset {
// 編碼方式
uint32_t encoding;
// 集合中包含的元素數量
uint32_t length;
// 真正保存元素的數組
int8_t contents[];
} intset;
content數組是整數集合的底層實現:整數集合的每個元素都是content數組的一個數組項,各個項在數組中按值的大小從小到大有序地排列,並且數組中不包含任何重複項。content數組的真正類型取決於encoding屬性的值。
2、升級
升級整數集合並添加新元素共分爲三步進行:
1)根據新元素的類型,擴展整數集合底層數組的空間大小,並未新元素分配空間。
2)將底層數組現有的所有元素都轉換成與新元素相同的類型,並將類型轉換後的元素放置到正確的位上,而且在放置元素的過程中,需要繼續維持底層數組的有序性質不變。
3)將新元素添加到底層數組裏面。
升級的好處:提升靈活性;節約內存
3、降級
整數集合不支持降級操作,一旦對數組進行了升級,編碼就會一直保持升級後的狀態
六、壓縮列表
1、壓縮列表的結構
2、壓縮列表節點的結構
previous_entry_length:記錄了壓縮列表中前一個點的長度。
encoding:記錄了節點的content屬性所保存數據的類型和長度。
content:保存節點的值,節點值可以是一個字節數組或者是整數,值的類型和長度由節點的encoding屬性決定。
3、連鎖更新
《redis設計與實現》P57