Redis系列之底層數(shù)據(jù)結(jié)構(gòu)SDS詳解
實驗的環(huán)境
- Redis 6.0
- VSCode 1.88.1
什么是SDS?
SDS:Simple Dynamic String,翻譯為簡單動態(tài)字符串。
SDS是一種用于存儲二進制數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu),具有動態(tài)擴容的特點,代碼位于src/sds.h
和src/sds.c
SDS的總體數(shù)據(jù)結(jié)構(gòu)大致如圖:在源碼里sds包括幾個部分,len
、alloc
、flags
、buf
,其中 sdshdr
是頭部,buf
是真實存儲數(shù)據(jù)的地方,在存儲的數(shù)據(jù)后面會跟一個\0
,所以數(shù)據(jù)加上\0
就是所謂的buf
- len:保存了SDS字符串的長度
- buf[]:保存數(shù)據(jù)的地方
- alloc:分別以uint8, uint16, uint32, uint64表示整個SDS
- flags:始終為一字節(jié), 以低三位標(biāo)示著頭部的類型, 高5位未使用
查看源碼sds.h,可以看到SDS里面有幾種不同的頭部,其中sdshdr5
實際并未使用到,所以實際上有四種不同的頭部
/* Note: sdshdr5 is never used, we just access the flags byte directly. * However is here to document the layout of type 5 SDS strings. */ struct __attribute__ ((__packed__)) sdshdr5 { unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; /* used */ uint8_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr16 { uint16_t len; /* used */ uint16_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr32 { uint32_t len; /* used */ uint32_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr64 { uint64_t len; /* used */ uint64_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; };
為什么要使用SDS?
Redis是用C語言寫的,為什么不直接就用C語言里的char
來定義字符串?
獲取字符串長度
由于有len
屬性,所以獲取SDS
字符串的長度只需要讀取len
屬性,所以時間復(fù)雜度為O(1)
。
如果直接使用C
語言中的字符串來實現(xiàn),獲取字符串的長度需要遍歷計數(shù),時間復(fù)雜度為O(n)
。
避免緩存區(qū)溢出
在C
語言中,如果使用strcat
函數(shù)來進行兩個字符串的拼接,如果沒有分配足夠長度的內(nèi)存空間,就會造成緩存區(qū)溢出。
而對于SDS
數(shù)據(jù)類型,在進行字符串修改的時候,會根據(jù)記錄的len屬性檢查內(nèi)存空間是否滿足需求,如果不滿足,會進行相應(yīng)空間的擴展,所以不會出現(xiàn)緩存區(qū)溢出
減少字符串內(nèi)存重新分配次數(shù)
在C
語言中字符串,是不會記錄字符串的長度的,所以一旦修改了字符串,就需要重新分配內(nèi)存,因為如果沒有重新分配,字符串長度增大時會造成內(nèi)存溢出區(qū)溢出,長度減小時會造成內(nèi)存泄漏。
而對于SDS來說,因為有長度熟悉len
和alloc
屬性的存在,SDS
實現(xiàn)了空間預(yù)分配和惰性空間釋放兩種策略來減少重新分配內(nèi)存
- 空間預(yù)分配:SDS對空間進行擴展的時候,擴展的內(nèi)存比實際需要的多,這樣可以減少字符串增長操作所需的內(nèi)存重新分配次數(shù)
- 惰性空間釋放:SDS對字符串進行縮短操作時,不會立即進行內(nèi)存重新分配,來回收縮短后多余的內(nèi)存空間,而是使用
alloc
將這些字節(jié)數(shù)量記錄下來,等待后續(xù)使用
二進制安全
在C
語言中,是以空字符串作為字符串結(jié)束的標(biāo)識,但是一些特殊的字符串,可能就包括空字符串的,所以容易丟失數(shù)據(jù),不能正確存取。
而SDS是根據(jù)len
屬性,以處理二進制的方式來處理buf
里的數(shù)據(jù),所以保存數(shù)據(jù)更加安全
兼容部分C字符串函數(shù)
SDS可以重用C語言庫<string.h>
中的一部分函數(shù)
C字符串和SDS對比
C字符串 | SDS |
---|---|
獲取字符串長度時間復(fù)雜度為O(n) | 獲取字符串的長度時間復(fù)雜度為O(1) |
不安全,可能會造成緩沖區(qū)溢出 | 安全,不會造成緩沖區(qū)溢出 |
修改字符串n次就需要進行n次內(nèi)存分配 | 修改字符串長度n次,最多需要n次內(nèi)存分配 |
只能保存文本數(shù)據(jù) | 可以保存文本數(shù)據(jù)或者二進制數(shù)據(jù) |
可以使用所有<string.h>庫中的函數(shù) | 可以使用一部分<string.h>庫中的函數(shù) |
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Redisson之lock()和tryLock()的區(qū)別及說明
這篇文章主要介紹了Redisson之lock()和tryLock()的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12NoSQL和Redis簡介及Redis在Windows下的安裝和使用教程
這篇文章主要介紹了NoSQL和Redis簡介及Redis在Windows下的安裝和使用教程,本文同時講解了python操作redis,并給出了操作實例,需要的朋友可以參考下2015-01-01Redis高并發(fā)緩存設(shè)計問題與性能優(yōu)化
本文詳細介紹了Redis緩存設(shè)計中常見的問題及解決方案,包括緩存穿透、緩存失效(擊穿)、緩存雪崩、熱點緩存key重建優(yōu)化、緩存與數(shù)據(jù)庫雙寫不一致以及開發(fā)規(guī)范與性能優(yōu)化,感興趣的可以了解一下2024-11-11Redis與MySQL數(shù)據(jù)一致性問題的策略模式及解決方案
開發(fā)中,一般會使用Redis緩存一些常用的熱點數(shù)據(jù)用來減少數(shù)據(jù)庫IO,提高系統(tǒng)的吞吐量,本文將給大家介紹了Redis與MySQL數(shù)據(jù)一致性問題的策略模式及解決方案,文中通過代碼示例介紹的非常詳細,需要的朋友可以參考下2024-07-07