startindex 不能大于字符串长度_玩转云端丨redis的5种对象与8种数据结构之字符串对象(下)...
引言
本文是對《redis設計與實現(第二版)》中數據結構與對象相關內容的整理與說明。本篇文章只對對象結構,1種對象——字符串對象。以及字符串對象所對應的兩種編碼——raw和embstr,進行了詳細介紹。表達一些本人的想法與看法,也希望更多朋友一起來討論,分享交流。
作者:太陽
云掣科技-數據庫團隊數據庫工程師
字符串對象
字符串對象可以存儲整數、浮點數、字符串,具體策略是:
當存儲整數時,用到的編碼是int,底層的數據結構可以用來存儲long類型的整數;當存儲字符串時,如果字符串的長度小于等于32字節,那么將用編碼為embstr的格式來存儲;如果字符串的長度大于32字節,將用編碼為raw的SDS格式來存儲;當存儲浮點數時會先將浮點數轉換為字符串,如果轉換后的字符串長度小于32字節就用編碼為embstr的格式來存儲,否則用編碼為raw的SDS格式來存儲。
下圖是一個字符串對象的結構圖,最左側是對象結構,中間跟右側合起來是raw編碼的SDS數據結構(sdshdr),示例圖:
raw編碼,簡單動態字符串(simple dynamic string-SDS)
redis用的并不是C語言傳統的字符串,而是自己構建了簡單動態字符串(simpledynamic string,SDS)。
當redis打印日志信息或輸出報錯信息,這些輸出的字符串是不會被修改的字符串字面量(sting literal),此時用的是C語言傳統的字符串來存儲這些信息的。當redis需要存儲的是可以被修改的字符串時,就會使用SDS結構。
除了用來保存數據庫中的字符串值之外,SDS還被用作緩沖區(buffer):AOF模塊中的AOF緩沖區,以及客戶端狀態中的輸入緩沖區,都是由SDS實現的。
SDS的結構
SDS結構示意圖如下所示:
sdshdr是該數據結構的名稱即SDS,其中:
buf屬性,是一個字節數組,用來保存字符串,后面箭頭對應的就是實際保存的字符串內容,最后以’0’空字符串結尾;
len屬性,記錄的是buf數組中實際已使用的字節數量,等于SDS所保存字符串的長度;
free屬性,記錄的是buf數組中未使用字節的數量。
SDS優點
一、可以用O(1)的復雜度獲取到字符串長度
SDS的len屬性記錄了字符串的長度,而傳統C字符串要想知道長度需要遍歷整個字符串。相比于傳統C字符串,redis獲取字符串長度所需的復雜度從O(N)降低到了O(1)。
即使對非常長的字符串反復執行STRLEN命令(獲取字符串長度),也不會造成過多的性能消耗。
二、杜絕緩沖區溢出
在傳統的C字符串中,如果要修改字符串的內容,但修改后字符串的長度超過原先的長度就會發生溢出現象。詳見下圖:
在SDS中,當需要對buf字節數組中存儲的內容進行修改(增添或刪除)時,API會先通過free和len屬性檢查SDS的空間是否足夠,如果不夠的話,SDS會自動擴展空間再對內容進行修改。關于自動擴展空間的策略見下方“空間預分配”的內容。三、減少修改字符串長度時所需的內存重分配次數
對于傳統C字符串:
如果執行的是增長字符串的操作,如拼接操作(append),那么在執行命令之前,程序需要先通過內存重分配來擴展底層數據的空間大小——否則會產生緩沖區溢出。
如果執行的是縮短字符串的操作,如截斷操作(trim),那么在執行這個操作之后,程序需要通過內存重分配來釋放字符串不再使用的空間——否則會產生內存泄漏。
對于redis中的SDS結構:
內存重分配設計復雜的算法,是一個比較耗時的操作,redis作為速度要求嚴苛、數據會被頻繁執行的數據庫,如果每次修改字符串都需要進行一次內存重分配,會嚴重影響性能。
使用SDS,buf數組里可以包含未使用的字節,這些字節的數量由free屬性記錄,可以減少修改字符串長度時所需的內存重分配次數。
空間預分配和惰性空間釋放
通過SDS中free屬性定義的未使用空間,SDS可以實現空間預分配和惰性空間釋放兩種優化策略:
1、空間預分配策略——可以降低字符串增長操作引起的內存重分配
當需要修改SDS的內容,且需要進行空間擴展的時候,程序不僅會為SDS分配修改所需的必須空間,還會為SDS分配額外的未使用空間。
其中,額外分配的未使用空間數量由以下公式決定:
如果對SDS進行修改之后,SDS的長度(即len屬性的值)將小于1MB,那么程序將分配和len屬性同樣大小的未使用空間,這時SDS len屬性的值將和free屬性的值相同。
如果對SDS進行修改后,SDS的長度將大于等于1MB,那么程序會分配1MB的未使用空間。
說明
如果對一個字符串的末尾持續追加內容,當字符串整體大小大于1MB時,即使只追加一字節的字符,程序也會額外分配1MB的空間,當再次追加一字節的字符時,程序不會再額外分配1MB的空間,而是使用已有的空閑空間。
即在擴展空間之前,會先檢查未使用的空間是否足夠,如果足夠,是不會額外再擴展的。
通過空間預分配策略,SDS將連續增長N次字符串所需的內存重分配次數從必定N次降低為最多N次。
2、惰性空間釋放策略——可以降低字符串縮短操作引起的內存重分配
當SDS中的字符串長度被縮短時,程序并不會立即使用內存重分配來回收縮短后多出來的字節空間,而是使用free屬性將這些字節的數量記錄起來,以備將來使用。
當然,redis提供了相應的命令來真正釋放這些未使用空間,避免不必要的內存浪費。
四、二進制安全
C字符串中的字符必須符合某種編碼(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,如果字符串除末尾外還有其它空字符,那么最先被程序讀入的空字符將被誤認為是字符串結尾,這些限制使得C字符串只能保存文本數據,而不能保存圖片、音頻、視頻、壓縮文件這樣的二進制數據。
為了確保redis可以適用于各種不同的使用場景,SDS的API都是二進制安全的(binary-safe),所有SDS API都會以處理二進制的方式來處理SDS存放buf數組里的數據,程序不會對其中的數據做任何限制、過濾或者假設,數據在寫入時是什么樣的,它被讀取時就是什么樣。
這也是RDS的buf屬性被稱為字節數組的原因——redis不是用這個數組來保存字符,而是用它來保存一系列二進制數據。五、兼容部分C字符串函數
SDS遵循空字符串結尾這一慣例,好處是可以直接重用C字符串<string.h>函數庫里的函數,從而避免了不必要的代碼重復。
embstr編碼
如果字符串對象保存的是長度小于等于32字節的字符串,那么將會使用embstr編碼,embstr編碼是專門用來保存短字符串的一種優化編碼方式。embstr編碼與raw編碼對應的字符串對象,都是由對象結構(redisObject)和數據結構(sdshdr)組成的。
區別在于用raw編碼的字符串對象會調用兩次內存分配函數來分別創建redisObject結構和sdshdr結構,而embstr編碼則通過調用一次內存分配函數來分配一塊連續的空間,空間中一次包含redisObject和sdshr兩個結構,embstr編碼的字符串對象結構圖如下所示:
兩者的區別
embstr編碼的字符串對象在執行命令時,產生的效果和raw編碼的字符串對象執行命令時產生的效果是相同的的,但使用embstr編碼的字符串對象來保存短字符串值有以下好處:
1、embstr編碼將創建字符串對象所需的內存分配次數從raw編碼的兩次降低為一次;
2、釋放embstr編碼的字符串對象只需要調用一次內存釋放函數,而釋放raw編碼的字符串對象需要調用兩次內存釋放函數;
3、embstr編碼的字符串對象的所有數據都保存在一塊連續的內存里,結構更加緊湊,而raw編碼是分散開的,redisObject對象結構和sdshdr數據結構彼此間是用指針相關聯的,embstr編碼的對象比raw編碼的對象能夠更好的利用緩存帶來的優勢。
編碼的轉換
int編碼的字符串對象和embstr編碼的字符串對象在條件滿足的情況下,會被轉換成raw編碼的字符串對象。encoding命令可以查看鍵對應的值,底層用的是什么編碼。
int轉換為raw
對于int編碼的字符串對象來說,如果我們向對象執行了一些命令,使得這個對象保存的不再是整數值,而是一個字符串值,那么字符串對象的編碼將從int變為raw。
27.0.0.1:6379> set a 100 //設置a=100 OK 127.0.0.1:6379> object encoding a //查看鍵a存儲的值用的是什么編碼 "int" 127.0.0.1:6379> append a 'a' //向鍵a的值中追加內容’a’,此時鍵a存儲的值將變為字符串類型 (integer) 4 127.0.0.1:6379> get a //查詢鍵a的值 "100a" 127.0.0.1:6379> object encoding a //查看鍵a存儲的值現在對應的編碼,發現已經變為raw格式的編碼,表示里面現在存儲的是字符串 "raw"int編碼的字符串,存儲的是long類型的整數,范圍是2^63-1(2的63次方減一) ~ -2^63(2的63次方),當存儲的整數在該范圍內時,編碼為int,當值超過該范圍,編碼將轉換為embstr。
27.0.0.1:6379> set number1 9223372036854775807 OK 127.0.0.1:6379> object encoding number1 "int" 127.0.0.1:6379> set number2 9223372036854775808 OK 127.0.0.1:6379> object encoding number2 "embstr" 127.0.0.1:6379> set number3 -9223372036854775808 OK 127.0.0.1:6379> object encoding number3 "int" 127.0.0.1:6379> set number4 -9223372036854775809 OK 127.0.0.1:6379> object encoding number4 "embstr"embstr轉換為raw
embstr編碼的字符串對象無法被修改(redis沒有為embstr編碼的字符串對象編寫任何響應的修改程序),只有int、raw編碼的字符串對象可以被修改,所以embstr編碼的字符串實際上是只讀的。
當對embstr編碼的字符串對象執行任何修改命令時,程序都會先將對象的編碼從embstr轉換為raw,然后再執行修改命令。所以一旦embstr編碼的字符串被修改,它的數據結構就會變成raw編碼的格式。
碎碎念
以上就是根據《redis設計與實現(第二版)》中數據結構與對象相關內容進行的部分整理與分享,歡迎各位共同參與討論一起交流溝通。
Redis系列會在未來與大家見面!
總結
以上是生活随笔為你收集整理的startindex 不能大于字符串长度_玩转云端丨redis的5种对象与8种数据结构之字符串对象(下)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 女子58同城找人疏通马桶报价60元:付款
- 下一篇: r语言主成分分析_PCA主成分分析