2021開工第一天,就有小夥伴私信我,還給我分享了一道他面阿里的redis
題(這傢伙絕比已經拿到年終獎了),我看了之後以爲挺有意思,題目很簡單,是那種典型的似懂非懂,經常容易被你們忽略的問題。這裏整理出來分享一下,順便本身鞏固一下基礎,但願對正在面試和想要面試的兄弟有點幫助。程序員
題目大體是這樣的面試
面試官:瞭解redis
的String
數據結構底層實現嘛?redis
鐵子:固然知道,是基於SDS
實現的算法
面試官:redis
是用C
語言開發的,那爲啥不直接用C
的字符串,還單獨設計SDS
這樣的結構呢?數據庫
鐵子:·····數組
其實看得出面試官是想看看,鐵子是隻停留在redis的使用層面,仍是對底層數據結構有過更深刻的研究,面試嘛都愛這樣問你們都懂得。
咱們知道redis
是用C
寫的,但它卻沒有徹底直接使用C
的字符串,而是本身又從新構建了一個叫簡單動態字符串SDS
(simple dynamic string)的抽象類型。數據結構
redis
也支持使用C
語言的傳統字符串,只不過會用在一些不須要對字符串修改的地方,好比靜態的字符輸出。併發
而咱們開發中使用redis
,每每會常常性的修改字符串的值,這個時候就會用SDS
來表示字符串的值了。有一點值得注意:在redis數據庫中,key-value
鍵值對含有字符串值的,都是由SDS
來實現的。函數
好比:在redis
執行一個最簡單的set
命令,這時redis
會新建一個鍵值對。高併發
127.0.0.1:6379> set xiaofu "程序員內點事"
此時鍵值對的key
和value
都是一個字符串對象,而對象的底層實現分別是兩個保存着字符串xiaofu
和程序員內點事
的SDS
結構。
再好比:我向一個列表中壓入數據,redis 又會新建一個鍵值對。
127.0.0.1:6379> lpush xiaofu "程序員內點事" "程序員小富"
這時候鍵值對的鍵和上邊同樣,仍是一個由SDS實現的字符串對象,鍵值對的值是一個包含兩個字符串對象的列表對象了,而這兩個對象的底層也是由SDS實現。
一個SDS值的數據結構,主要由len
、free
、buf[]
這三個屬性組成。
struct sdshdr{ int free; // buf[]數組未使用字節的數量 int len; // buf[]數組所保存的字符串的長度 char buf[]; // 保存字符串的數組 }
其中buf[]
爲實際保存字符串的char
類型數組;free
表示buf[]數組未使用字節的數量;len
表示buf[]數組所保存的字符串的長度。
例如上圖表示的是buf[]
保存長度爲6個字節的字符串,未使用的字節數free
爲0,可是眼尖的同窗會發現這明明是7個字符,還有一個"\0"
啊?
上邊提到過SDS
沒有徹底直接使用C
的字符串,仍是沿用了一些C特性的,好比遵循C
的字符串以空格符結尾的規則,這樣還可使用一部分C字符串的函數。而對於SDS來講,空字符串佔用的一字節是不計算在len
屬性裏的,會爲他分配額外的空間。
簡單瞭解SDS結構後,下邊咱們來看看SDS相比於C字符串有哪些優勢。
舉個例子:工做中使用redis
,常常會經過STRLEN
命令獲得一個字符串的長度,在SDS
結構中len
屬性記錄了字符串的長度,因此咱們獲取一個字符串長度直接取len
的值,複雜度是O(1)。
而若是用C字符串,在獲取一個字符串長度時,需對整個字符串進行遍歷,直至遍歷到空格符結束(C中遇到空格符表明一個完整字符串),此時的複雜度是O(N)。
在高併發場景下頻繁遍歷字符串,獲取字符串的長度頗有可能成爲redis
的性能瓶頸,因此SDS性能更好一些。
上邊提到C字符串是不記錄自身長度的,相鄰的兩個字符串存儲的方式可能以下圖,爲字符串分配了合適的內存空間。
若是此時我想把「程序員內點事」
改爲「程序員內點事123」
,可以前分配的內存只有6個字節,修改後的字符串須要9個字節才能放下啊,怎麼搞?
沒辦法只能侵佔
相鄰字符串的空間,自身數據溢出致使其餘字符串的內容被修改。
而SDS很好的規避了這點,當咱們須要修改數據時,首先會檢查當前SDS空間len
是否知足,不知足則自動擴容空間至修改所需的大小,而後再執行修改,以下圖所示。
不過有個特殊的地方,在把「程序員內點事」
的6個字節擴容到「程序員內點事123」
9個字節後,發現free
屬性的值變成了擴容後字符串的總長度,這就涉及到下邊要說的內存重分配策略了。
C字符串長度是必定的,因此每次在增加或者縮短字符串時,都要作內存的重分配,而內存重分配算法一般又是一個比較耗時的操做,若是程序不常常修改字符串仍是能夠接受的。
但很不幸,redis
做爲一個數據庫,數據確定會被頻繁修改,若是每次修改都要執行一次內存重分配,那麼就會嚴重影響性能。
SDS經過兩種內存重分配策略,很好的解決了字符串在增加和縮短時的內存分配問題。
空間預分配策略用於優化SDS字符串增加操做,當修改字符串並需對SDS的空間進行擴展時,不只會爲SDS分配修改所必要的空間,還會爲SDS分配額外的未使用空間free
,下次再修改就先檢查未使用空間free
是否知足,知足則不用在擴展空間。
經過空間預分配策略,redis
能夠有效的減小字符串連續增加操做,所產生的內存重分配次數。
額外分配未使用空間free
的規則:
len
值小於 1M
,那麼此時額外分配未使用空間 free
的大小與len
相等。len
值大於等於 1M
,那麼此時額外分配未使用空間 free
的大小爲1M
。惰性空間釋放策略則用於優化SDS字符串縮短操做,當縮短SDS字符串後,並不會當即執行內存重分配來回收多餘的空間,而是用free
屬性將這些空間記錄下來,若是後續有增加操做,則可直接使用。
C字符串中的字符必須符合某些特定的編碼格式,並且上邊咱們也提到,C字符串以\0
空字符結尾標識一個字符串結束,因此字符串裏邊是不能包含\0
的,否則就會被誤認是多個。
因爲這種限制,使得C字符串只能保存文本數據,像音視頻、圖片等二進制格式的數據是沒法存儲的。
redis 會以處理二進制的方式操做Buf
數組中的數據,因此對存入其中的數據作任何的限制、過濾,只要存進來什麼樣,取出來仍是什麼樣。
上邊只是 redis 數據結構的一點基礎知識,沒什麼難度,但以個人面試經驗,若是被問這類問題,不要只含糊其辭的說出底層是SDS,有理有據的把爲何這樣實現也說出來。
一來能夠顯得本身基本功紮實,若是表達的在條理清晰,是個很不錯的加分項;在一個主動打消面試官問下去的念頭,固然就怕不按套路出牌的人!
整理了幾百本各種技術電子書,有須要的同窗公號[ 程序員內點事 ]內回覆[ 666 ]自取。技術羣快滿了,想進的同窗能夠加我好友,和大佬們一塊兒吹吹技術。