Redis-簡單動態字符串(SDS)

2021-03-02 Ross的記錄空間
1.何為SDS

  SDS(simple dynamic string)又名簡單動態字符串,是Redis的基本數據結構,Redis中包含字符串的鍵值對的底層均是SDS實現。眾所周知,Redis使用C語言編寫的,至於Redis為何不用C語言中的字符串表示,且聽我娓娓道來。

2.定義

下面我們來看看SDS的結構:

struct sdshdr{
  // 記錄了SDS保存的字符串的長度,換句話說就是buf數組中使用的字節數
  int len;
  // buf數組中未使用字節數
  int free;
  // 用來保存字符串的字節數組
  char bur[];
};

圖1-SDS結構圖示

  需要注意的是SDS中的buf數組雖然以空字符'\0'結尾,但是空字符的1位元組空間是不會計算在SDS的len屬性中,SDS在分配空間時會額外分多1位元組空間給空字符。由於SDS同C字符串一樣遵循空字符結尾,所以可以直接對SDS使用C語言的函數進行操作。

3.與C字符串的區別

  下面我們將對比SDS與C字符串的區別,解釋Redis選擇SDS而非C字符串的原因。

3.1獲取字符串長度僅需常數複雜度

  由於C字符串中無記錄自身長度信息的屬性,所以每次獲取C字符串的長度都需要程序從頭開始遍歷整個字符串直到遇到空字符,所以C字符串獲取自身長度的複雜度為O(N)

  反觀SDS,Redis想獲取字符串長度只需訪問SDS的len屬性即可,其複雜度為O(1)

3.2杜絕緩衝區溢出

  因C字符串不記錄自身長度,從而容易引發緩衝區溢出的問題——當給字符串分配的內存不足時,字符串的數據就會溢出到其他數據所處的空間之中,造成數據被修改。

  SDS的API在執行對SDS的修改時,會先檢查SDS的空間是否滿足修改的要求,不滿足的話API會自動將SDS的空間進行擴充至符合修改要求。

3.3減少修改字符串時帶來的內存重分配次數

  也因為C字符串不記錄自身長度的緣故,每次縮短或擴展一個字符串時,程序會對C字符串的數組進行內存重分配操作,主要分為append(拼接)和trim(截取),append時未擴充底層數組空間則會造成緩衝區溢出,trim時忘記釋放底層數組空間則會造成內存洩露

  為了不踩C字符串的坑,SDS實現了空間預分配和惰性空間釋放兩種策略:

空間預分配

空間預分配用於解決SDS擴展的問題:對SDS進行擴展時,API不僅分配SDS修改所需空間,而且為SDS分配額外的未使用空間。修改後空間小於1MB時,修改後的len屬性將與free屬性相同;大於等於1MB時,將額外分配多1MB額外空間,下圖是將圖1的Ross字符串修改為Jim Ross的情況:

圖2-Ross字符串修改為Jim Ross

惰性空間釋放

惰性空間釋放用於解決SDS縮短的問題:API在縮短SDS時,不是直接將內存重分配來回收縮短空間,而是將多餘空間用free屬性記錄起來給以後使用,下圖是將圖2中的Jim Ross修改為Jim的情況:

圖3-Jim Ross修改為Jim3.4 二進位安全

  舉一個例子,假如我們有一個由多個空字符隔開的字符串比如一個英文句子,如果我們使用C字符串存儲則只會識別到第一個空字符就停止了。若我們使用SDS,API則會以處理二進位的方式處理SDS中buf數組的數據,程序不對其中的數據做任何限制。SDS是以len屬性為標準判斷一個字符串是否結束而不是空字符。

3.5兼容部分C字符串函數

  前面我們已經提到了我們可以直接對SDS使用C語言的函數進行操作,具體是C語言中的<string.h>中的部分函數比如strcasecmp、strcat等。

後記

  由於本人能力有限,如有不當或錯誤的地方,歡迎大家批評指正。

參考

《Redis設計與實現》

相關焦點

  • GDB 調試實戰之 Redis 通信協議
    (c->flags & REDIS_BLOCK)) || (errno == EINTR)) { /* Try again later */ } else { __redisSetError(c,REDIS_ERR_IO,NULL); return
  • Redis 小白入門以及基礎搭建
    Redis 數據類型Redis 支持的數據類型主要有五種,它們分別是string 字符串 是 Redis 中最簡單的數據類型,它是與 Memcached 一樣的類型,一個 key 對應一個 value,這種 key/value 對應的方式稱為鍵值對。
  • Redis 管理實戰
    1、Redis 基本數據類型類型說明String  字符串Redis 字符串數據類型的相關命令用於管理 redis 字符串值HashList  列表Redis列表是簡單的字符串列表,按照插入順序排序。你可以添加一個元素到列表的頭部(左邊)或者尾部(右邊) 一個列表最多可以包含 232 - 1 個元素 (4294967295, 每個列表超過40億個元素)。
  • 關於redis,學會這8點就夠了
    該資料庫使用ANSI C語言編寫,支持網絡,提供字符串、哈希、列表、隊列、集合結構直接存取,基於內存,可持久化。二、支持的語言三、redis的應用場景有哪些1、會話緩存(最常用)2、消息隊列,比如支付3、活動排行榜或計數4、發布、訂閱消息(消息通知)5、商品列表、評論列表等四、redis數據類型Redis一共支持五種數據類:string(字符串)、hash(哈希)、list(列表)、set(集合)和zset(sorted
  • Redis(四):del/unlink 命令源碼解析
    下面,我們就來看看 redis 是如何具體釋放內存的吧。先看看 key destructor, key 的釋放// server.c, 直接調用 sds 提供的服務即可void dictSdsDestructor(void *privdata, void *val){ DICT_NOTUSED(privdata); // sds 直接釋放key就行了 sdsfree(val);}// sds.c, 真正釋放 value 內存/* Free
  • Redis入門教程
    如果你想切換回默認資料庫,只要在命令行界面鍵入select 0即可1.2 命令、關鍵字和值Redis不僅僅是一種簡單的關鍵字-值型存儲,從其核心概念來看,Redis的5種數據結構中的每一個都至少有一個關鍵字和一個值關鍵字(Keys)是用來標識數據塊,值(Values)是關聯於關鍵字的實際值,可以是字符串、整數、序列化對象(使用JSON、XML
  • 17.Go語言編程之go-redis操作Redis內存資料庫實踐
    Redis 字符串數據類型的相關命令用於管理 redis 字符串值// 設置key並指定字符串(String)127.0.0.1:6379> set myname "weiyigeek" EX 60OK// 獲取指定key存在的字符串
  • Java字符串String那些事
    在JDK8 hotspot移除了永久代用元空間(Metaspace)取而代之, 這時候字符串常量池還在堆裡只不過把方法區的實現從永久代變成了元空間(Metaspace) 。字面量賦值:通過字面量賦值(使用雙引號聲明出來的String)會先去常量池中查找是否已經有相同的字符串,如果已經存在棧中的引用直接指向該字符串,如果不存在就在常量中生成一個字符串再將棧中的引用指向該字符串。new 的方式創建:而通過new的方式創建字符串時,就直接在堆中生成一個字符串的對象棧中的引用指向該對象。
  • 萬字長文的Redis五種數據結構詳解(理論+實戰)| 建議收藏
    舉一個簡單的例子,你在Redis中設置一個字符串key 234,然後查看這個字符串的存儲類型就會看到為int類型,非整數型的使用的是embstr儲存類型,具體操作如下圖所示:設置為raw;若是「字符串長度小於等於32個字節」就會將encoding改為embstr來保存字符串。
  • Go 使用三方 Redis 包操作 Redis
    01概念Redis 是一個基於內存的非關係型資料庫,在項目開發中使用非常廣泛,
  • Redis 內存壓縮原理
    127.0.0.1:6379> object encoding a "int" 127.0.0.1:6379> rpush l 1 (integer) 1 127.0.0.1:6379> object encoding l "ziplist"先總結一下各種數據結構可以使用的編碼類型,下文再對這些壓縮類型進行詳細說明:stringraw: 動態字符串
  • Redis是什麼?看這一篇就夠了
    其中,field 和 value 都是字符串類型的。   簡單動態字符串SDS (Simple Dynamic String)基於 C 語言中傳統字符串的缺陷,Redis 自己構建了一種名為簡單動態字符串的抽象類型,簡稱 SDS,其結構如下:
  • 完整的 string.Format對C#字符串格式化
    零佔位符:如果格式化的值在格式字符串中出現「0」的位置有一個數字,則此數字被複製到結果字符串中。小數點前最左邊的「0」的位置和小數點後最右邊的「0」的位置確定總在結果字符串中出現的數字範圍。「00」說明符使得值被捨入到小數點前最近的數字,其中零位總被捨去。數字佔位符:如果格式化的值在格式字符串中出現「#」的位置有一個數字,則此數字被複製到結果字符串中。
  • Golang 語言怎麼高效使用字符串?
    如果需要修改,通常的做法是對原字符串進行截取和拼接操作,從而生成一個新字符串,但是會涉及內存分配和數據拷貝,從而有性能開銷。本文我們介紹在 Golang 語言中怎麼高效使用字符串。欄位 str 是指向字節數組頭部的指針值,欄位 len 的值是字符串的長度(字節個數)。
  • python 通關字符串操作方法詳解-大量案例
    字符串是一個有序的字符的集合,用於存儲和表示基本的文本信息,一對單,雙或三引號中間包含的內容稱之為字符串。其中三引號可以由多行組成,編寫多行文本的快捷語法,常用文檔字符串,在文件的特定地點,被當做注釋。便捷的多行注釋。
  • Redis常用命令--set
    SET key value [EX seconds] [PX milliseconds] [NX|XX]將字符串值
  • Redis在python與django使用詳解
    string(字符串)list(鍊表)set(集合)zset(有序集合)hash(哈希類型)Redis 的好處特點好處:特點:與Memcached比較二、python 操作 Redis普通連接from redis import Redis# 簡單使用conn = Redis(host='127.0.0.1', port=6379)ret = conn.get('name') # 取出print(ret)
  • Redis 命令執行過程(上)
    處理為設置密碼時默認保護狀態的客戶端連接 // 統計連接數 server.stat_numconnections++; c->flags |= flags;}createClient 方法用於創建 client,它代表著連接到 Redis 客戶端,每個客戶端都有各自的輸入緩衝區和輸出緩衝區,輸入緩衝區存儲客戶端通過
  • Java –將字符串轉換為int
    Integer.parseInt()–將String轉換為int 3.1此示例將字符串999轉換為基本類型int 。NumberFormatException e) { System.err.println("Unable to convert input string :" + number + " to int"); } }} 輸出量 Terminal999 3.2對於字符串
  • Redis:我的一條命令是如何執行的?
    在之前的文章中《當 Redis 發生高延遲時,到底發生了什麼》我們曾簡單的描述了一條命令的執行過程,本篇文章展示深入說明一下,加深讀者對 Redis 的了解。client *createClient(int fd) { client *c = zmalloc(sizeof(client)); // fd 為 -1,表示其他特殊情況創建的client,redis在進行比如lua腳本執行之類的情況下也會創建client if (fd !