Redis—簡單動態字串(SDS)
這兩天我原本打算學習一下若依這個老生常談的開源的後端管理系統(如果你沒聽過,在將來的某一天也會聽說甚至用到),拿到這個系統的前後端分離版後,若依碼雲專案地址,一啟動發現需要Redis服務,遂轉戰學習Redis(Redis也是老生常談了~)
白澤預計將花費一個月的時間去學習Redis的設計與實現,並儘量保證文章的輸出,我們共勉~
本次介紹一個Redis的資料型別,簡單動態字串,SDS(simple dynamic string),因為Redis是用C語言寫的,而且C語言本身就有字串這個資料型別,那就要提出疑問:為啥不直接用C語言的字串,而要新發明一種新的字串型別呢?
事實上,即使我沒學過Redis,也聽聞過它可以以鍵值對的形式儲存資料,它很快。但它的快絕不僅僅是鍵值對的儲存形式帶來的,SDS的存在就是幫助Redis更快,更安全~
SDS的定義
//sds的儲存結構
struct sdshdr {
int len; //記錄buf陣列中已經使用的位元組的數量
int free; //記錄buf陣列中還未使用的位元組的數量
char buf[]; //位元組陣列,用於儲存字串
};
很明顯,SDS內部就是一個C語言的字串(位元組陣列),只是多了兩個變數存放當前的長度和剩餘的長度,下面這張圖模擬了當sds存放了 'Redis' 字串後的情況
SDS遵循C字串以空字元結尾的慣例,儲存空字元的1位元組空間不計算在SDS的len屬性中,好處是SDS可以直接重用一部分的C字串函式庫中的函式(只要SDS內部的buf也是以'\0'結尾,那就是一個C語言的字串啊,有啥不能用的~)
當然還可以像下面這張圖這樣,5為已使用的buf陣列的長度,5為未使用的陣列的長度,而下面這種場景才是更多的出現在Redis中的。接下來就講講通過free,len和C語言字串buf的配合,如何使Redis比單純使用C語言的字串更快,更安全吧
SDS與C字串的區別
1. 常數複雜度獲取字串長度:
因為SDS的free直接就記錄了buf陣列的使用長度,因此如果要獲取buf的長度,SDS只需要O(1)的時間複雜度,而C的字串需要O(N),因此更快!
2. 杜絕緩衝區溢位:
如上圖:因為C語言字串不記錄本身的長度,如果原本連續的記憶體中存放了str1 = 'Redis',str2 = 'MySQL'兩個字串,此時執行某個操作將str1替換為下方的str1 = 'Hello BaiZe',那麼str1的內容將會溢位到原本str2的空間中
如果使用SDS的API修改SDS內容時,如果buf剩餘空間不足,API將先擴容SDS的空間,然後再修改buf陣列的內容。因此更安全!
3. 減少修改字串時帶來的記憶體重分配次數
C語言的字串無論是增長還是縮短,每次都需要程式重新分配記憶體(因為可能會影響到記憶體已經存在的資料),這就意味著每次都將消耗一定資源去完成這個任務。
而Redis作為資料庫,資料可能會頻繁修改,每次都去記憶體重分配就慢了。而在SDS中,len、free和buf陣列相配合,就能從兩個角度去優化頻繁修改時時間的消耗
-
空間預分配:
當SDS需要擴容的時候(進行一次記憶體重新分配),擴容後len小於1MB,那麼程式分配和len屬性相同大小的free空間,則本次擴容後SDS的free會等於此時SDS的len,當然空字元'\0'依舊會佔額外的一位元組的空間,本次擴容結束後SDS所佔空間為:len + free + 1byte
如果擴容後len大於1MB,那麼程式會分配1MB的未使用free空間(所以最多就是給你分配1MB的free),此時SDS所佔用空間為:len + free(1MB) + 1byte
說到底空間預分配的目的就是每次擴容時多申請一些空間(每次擴容也是一次記憶體的重新分配),以備下次buf陣列長度增長時或許可以不去申請記憶體的重新分配,但最差的情況下每次多申請的空間都不夠下次用的,那依舊退化為C語言字串擴容時每次都需要重新分配記憶體的情況
-
惰性空間釋放
有擴容就有縮短,當SDS的buf變短時,程式並不直接進行記憶體的重新分配,而是隻是增加free的值,這比進行一次記憶體重新分配縮短buf快很多!buf陣列多餘的5個空間依舊保留,如果將來要對SDS進行增長操作還能用上。當然如果有需要SDS也提供了API對這些空間進行真正的釋放
4. 二進位制安全
C語言字串以空字元'\0'判斷字串是否結束,那麼下面這種含有兩個空字元的字串就無法完整地存入C語言的字串,因此也無法儲存二進位制資料(圖片、視訊、音訊等),但是SDS的長度是由len定義的,因此內部也可以存放空字元'\0',也可以用於儲存二進位制資料(啥都能存)
小結
C字串和SDS的區別