Redis 帝國的神祕使者,居然想改造 C 語言!


你好,我是悟空。程序員

迎接使者大人

「籲····」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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索