你好,我是悟空。程序員
迎接使者大人
「籲····」web
這聲音從一輛豪華馬車中傳出,拉車的兩匹馬兒聽到後,立馬停在了路邊。算法
「先生,可有什麼不對勁?」車伕謹慎地問道。小程序
車中的一位年輕帥小夥拉開了車門前的簾布,說道:「前方有一隻百人軍隊正在趕來,想必是 C 語言帝國的皇家護衛軍。」數組
一小會的功夫,前方百人軍隊正騎着馬來到了馬車前。緩存
一名身材魁梧,八尺高,手持一柄長槍的士兵從馬背上下來了。微信
「我是 C 語言帝國的皇家護衛隊隊長,恭聞使者大人遠道而來出使我國,國王特派我前來迎接。」 這位隊長笑盈盈說道。數據結構
C 語言帝國大殿
「使者大人,前面就是我國的宮殿了,請當心殿堂內的字符串大臣
。」護衛隊隊長說道。架構
先生心生疑惑地走進了殿堂中,你們的目光都匯聚到了這位年輕人的身上。他在大殿上給國王行了一個禮。編輯器
國王說道:「這是 Redis 帝國派來的使者,他給咱們帶了一個新的數據結構,叫作簡單動態字符串
,他還有個英文名字,叫作 SDS
,全稱 Simple Dynamic String」。
在大殿一旁的字符串大臣,臉色顯得略微有點難看。
國王繼續說道:「SDS 先生,你一路辛苦了,能夠介紹下貴國的 SDS 數據結構嗎?」
SDS 使者說:「我和 C 語言大國的字符串不同,咱們先來回顧下貴國的字符串表示方式。C 語言字符串是由字符數組
組成的,最後一個元素老是空字符 \0
。」 使者向殿內大臣展現了一張示意圖:
「好比如今中文悟空
的拼音 wukong
被放到數組一個長度爲 7 的字符數組中,最後一個元素是空字符'\0'。」 使者繼續解釋道。
旁邊的數組大臣和字符串大臣專一地聆聽着,好像隨時準備發問似的。
SDS 使者說:「咱們 Redis 帝國的字符串是用 SDS 表示的,這是在字符數組上進行了加強,是一種新的結構體」
隨後使者拿出了一卷羊皮紙,上面寫着一份代碼:
struct sdshdr {
// 字符數組,用於保存字符串
// 和 C 語言中保存字符串的字符數組同樣。
char buf[];
// 記錄 buf 數組中已使用字節的數量,等於 SDS 鎖保存字符串的長度。
int len;
// 記錄 buf 數組中未使用字節的數量
int free;
}
隨後 SDS 使者拿出來了一張準備好的示例圖解釋道:
簡單動態字符串 SDS 結構是由三部分組成的:
-
buf 數組屬性和 C 語言帝國同樣,都用了 7 個字節來保存 wukong
,最後一個元素是空字符。 -
len 屬性這個值爲 6,表明這個 SDS 保存了一個 6 字節長的字符串(最後一個空字符不計算 len 屬性中)。 -
free 屬性的值爲 0,表明這個 SDS 沒有其餘剩餘空間來存放字符了。
注意:數組中的空字符是自動加到字符串末尾的,由 SDS 的函數自動完成。爲何要和 C 語言的字符串的空字符結尾保持一致呢?是由於這樣能夠重用一部分 C 字符串函數庫裏面的函數。
旁邊的字符串大臣按捺不住地問道:「你這樣作,我看不到什麼好處呢?反而增長了空間來保存 free 和 len 屬性。」
衆人聽完字符串大臣的話,都如有所思。
「你們不要着急,且聽使者解釋。」國王看着衆人疑惑的臉說道。
「由於我用 len 屬性記錄了字符串的總長度,因此要是有程序想要訪問 SDS 的 len 屬性,就能夠當即知道保存的字符串長度,簡單來講就是複雜度爲 O(1)。好比我剛剛舉的例子,能夠當即知道 SDS 的長度爲 6 字節。」 使者不緊不慢地說道。
國王將目光投向了字符串大臣,而後說道:「字符串愛卿,咱們的 C 字符串計算長度須要多久?」
「尊敬的國王大人,咱們計算 C 字符串的長度須要遍歷整個字符串,對遇到的每一個字符進行計數,直到遇到表明字符串結尾的空字符爲止。上面的例子,咱們要記 6 次,也就是複雜度爲 O(N)。」 字符串大臣連忙回答。
「那咱們也太慢了吧...」 國王小聲嘀咕着。
內存分配的天賦
杜絕緩衝區溢出
「據說 SDS 在內存分配上有很大的天賦,能夠給咱們說說看嗎?」C 語言帝國的內存大臣提到。
「首先我能夠杜絕緩衝區溢出。」 SDS 使者自豪地說道。
提示:緩衝區是對原始磁盤塊的臨時存儲,用來緩存將要寫入磁盤的數據。這樣,內核就能夠把分散的寫集中起來,統一優化磁盤寫入。
「快給我說說,我發現老是有緩衝區溢出的異常出現,就是由於 C 字符串的一些不正規操做致使的。」內存大臣說完瞥了一眼字符串大臣。
「這可無論個人事,都是那些程序員不正規操做形成的。」字符串趕忙向內存大臣解釋。
「這還跟程序員有關?」國王追問着。
「國王大人,狀況是這樣的,假設內存中有緊鄰
的 C 字符串 S1 和 S2,S1 保存了字符串 WuKong
(悟空),而 S2 字符串保存了字符串ZiXia
(紫霞),給您看個示意圖。」SDS 使者拿出了一張早已準備好的原理圖:
「若是某個程序員執行了以下拼接字符串命令,又沒有提早爲 S1 分配足夠的空間,那就悲劇了。」字符串大臣繼續說道。
strcat(s1, " QuJing") // 取經
「由於 S1 沒有分配足夠的空間,因此在 strcat 函數執行以後,S1 d的數據將會溢出到 S2 所在的空間中,致使 S2 保存的內容被意外地修改,這就是緩衝區溢出。但這個跟我無關啊,是程序員乾的。」字符串大臣一臉無辜地說道。
「對對對,就是這樣,害得我好慘。」內存大臣嘀咕道。
「請問使者有什麼高見?」國王大人畢恭畢敬地說道。
「和 C 字符串相比,SDS 的空間分配策略就杜絕了這種狀況發生。」SDS 使者平靜地說道。
「當 SDS API,好比拼接的 API,須要對 SDS 進行修改時,API 會先檢查 SDS 的空間是否知足修改所需的要求,不知足的話,則會自動擴容。」 SDS 解釋道。
「妙啊!對於 C 字符串來講,每次修改字符串長度都要進行內存重分配的操做,涉及到了複雜的算法,還可能須要執行系統調用,很是耗時。」 內存大臣大聲感嘆道。
「那大家的自動擴容是每次修改字符串時都須要麼?」 字符串大臣疑惑道。
「固然不是,咱們擴容的時候不只會爲 SDS 分配修改所必需要的空間,並且還會爲 SDS 分配額外的未使用空間。」
「快給咱們講講吧。」國王急不可待的說道。
空間預分配
「我這個功能叫作空間預分配。分配的規則以下。」使者義正言辭地解釋道。
-
若是對 SDS 進行修改以後,SDS 的長度(len 屬性決定),仍是小於 1 MB 的話,那麼將會分配和 len 屬性相同大小的未使用空間,那麼 SDS len 屬性的值將 free 屬性的值相同。好比說當前 SDS 的 len = 6,加上 7 個字符後,len 的值變爲了 13,仍是小於 1 MB 的,而後 SDS 會被分配 13 字節的 未使用空間
。那麼 SDS 的實際長度就是 13 + 13 + 1 = 27 字節(額外的字節用於保存空字符)。 -
若是對 SDS 進行修改以後,SDS 的長度大於等於 1 MB,那麼會分配 1 MB 的額外空間。也就是說 SDS 長度等於必須存放的空間的長度 + 1MB 的未使用空間的長度。好比說 SDS 修改以後,變成了 10 MB,那麼會分配 1 MB 的未使用空間,最後 buf 數組的實際長度等於 10 MB + 1 MB + 1 byte。
聽完使者說完,國王仍是有點懵,可是身邊的字符串大臣和內存大臣已經聽懂了,不虧是 C 語言帝國的兩大支柱,這點擴容知識仍是很容易理解的。
「能否舉個例子呀,寡人實在沒法理解。」國王搖搖頭的說道。
「好的,國王。我仍是用悟空取經
來講明。」使者答應道。
「首先 SDS 存放的是悟空的英文 WuKong
,而後追加一個取經的英文QuJing
,咱們來看看怎麼擴容的。」使者繼續說道。
提示:SDS 的 API 中其實也有一個用於執行拼接操做的 sdscat 函數,他能夠將一個 C 字符串拼接到 SDS 所保存的字符串後面,可是在執行拼接操做以前,sdscat API 會先檢查給定 SDS 的空間是否足夠,若是不夠的話, sdscat 會先擴展 SDS 的空間,而後才執行拼接操做。
s = "WuKong";
sdscat( s , QuJing");
WuKong
總共佔了 6 個字符,QuJing
佔了 7 個字符(包含前面的空格)。最後拼接的結果就是:WuKong Qujing
。我仍是來畫個圖給你們看下吧。」
最開始 SDS 長這樣:
後來拼接了 " QuJing" 的時候,free = 0,不足以存放,因此開始擴容,首先會增長 7 個字符的空間,len = 6 + 7 = 13,而後再分配額外的未使用空間,空間大小等於 len 的值,也是 13,因此 free = len = 13。
拼接後 SDS 長下面這樣:
若是還想再拼接一個字符串在後面,好比字符串 GuiLai
(中文:歸來,佔 6 個字符),那是不用再擴容,由於未使用的 13 個字符串空間足以容納這 6 個字符。經過簡單的計算也能夠得出這個結果:Total = 13 + 13 = 26,6 + 7 + 7 = 20 < 26。
衆人聽完使者的解釋後,都讚歎不已。
忽然大殿之中傳來一個聲音:「那假設每次拼接的字符串都超過了未分配空間,是否是每次都得擴容?」衆人將目光轉向了聲音的方向,字符串大臣那裏。
「的確如此,不過經過這種預分配的擴容方式,SDS 將一定 N 次擴容下降爲最多 N 次。」使者微笑道。
「那縮短字符串的時候,會當即回收多餘的空間嗎?」字符串大臣追問道。
「咱們有惰性空間釋放
的功能,不會當即釋放多出來的字節,而是等待未來使用。並且當有須要的時候,會有專門的 API 來真正地釋放 SDS 的空間。」使者解釋道。
衆人紛紛點頭,國王總結道:「經過 SDS 的預分配和惰性空間釋放的方式,確實減小了分配空間的次數,難怪 Redis 會這麼快的。」
字符串大臣靜靜地聽着國王的總結,生怕國王一聲令下要改造 C 語言帝國的字符串形式。
國王對着字符串大臣說道:「你去公衆號回覆下 sds
吧,據說那裏能夠下載 Redis SDS 的中文註釋版的源碼。研究下,後面可能改造下咱們的字符串。」
巨人的肩膀:
Redis 設計與實現
500 人 Java、架構交流羣
掃碼加入,一塊兒進階。
本文分享自微信公衆號 - 悟空聊架構(PassJava666)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。