1. 程式人生 > 資料庫 >說說redis中簡單動態字串(SDS)的空間預分配實現

說說redis中簡單動態字串(SDS)的空間預分配實現

文章目錄

目的

  • 編寫本文章的目的是為了理解Redis底層實現的重要資料結構:簡單動態字串,並實際動手通過java程式碼實現簡單動態字串的空間預分配機制,讓我們更加生動地理解底層技術。

一、簡單動態字串(SDS)

1.1 定義

  • 簡單動態字串是Redis底層結構中非常關鍵的一類資料結構,定義如下:
struct sdshdr {
    // 字串陣列
    char[] buff;
   // buff字串陣列中已用的長度
   int len;
   // buff字串陣列中未用的長度
   int free;
}
  • 舉個例子: buff陣列中存放了“redis”5個字元,那buff字串陣列中已用的長度len即為5,但buff同時還預留了另外5個位元組空間,未存放任何字元,那預留的這5個位元組空間用free表示。
    在這裡插入圖片描述

1.1 優點

  • 相比普通的字串,使獲取字串長度時間複雜度降為O(1),即直接取len值就獲取到了字串的長度。
    在這裡插入圖片描述
  • 相比普通的字串,這種預留未分配空間的機制可以減少修改字串時帶來的記憶體重分配次數,加快了程式執行的速度。
    在這裡插入圖片描述

二、空間預分配

2.1 原則

  • 空間預分配用於優化SDS的字串增長操作:當SDS的API對一個SDS進行修改,並且需要對SDS進行空間擴充套件的時候,程式不僅會為SDS分配修改所必須要的空間,還會為SDS分配額外的未使用空間,分配原則如下:
  • 如果對SDS進行修改之後,SDS的長度(也即是len屬性的值)將小於1MB,那麼程式分配和len屬性同樣大小的未使用空間,這時SDS len屬性的值將和free屬性的值相同。
  • 如果對SDS進行修改之後,SDS的長度(也即是len屬性的值)將大於等於1MB,那麼程式會分配1MB的未使用空間

2.2 java程式碼實現

  • 建立SDS類
  • 編定api :新建,擴充套件空間,追加字串等
  • 測試api,檢查空間預分配機制
/**
 * Redis 簡單動態字串空間預分配的實現
 *
 * @author zhuhuix
 * @date 2021-01-12
 */
public class SDS {

    // 已使用的長度
    private int len;

    // 未使用的長度
    private int free;

    // 字元陣列
    private char[] buff;

    private SDS set(SDS sds) {
        this.buff = sds.buff;
        this.len = sds.len;
        this.free = sds.free;
        return this;
    }

    // 建立一個給定字元陣列的SDS
    public SDS sdsNew(char[] s) {
        this.len = s.length;
        this.free = 0;
        this.buff = new char[this.len];
        this.buff = Arrays.copyOf(s, this.len);
        return this;
    }

    // 按新長度給SDS重新分配空間
    public SDS sdsNewLen(int newLen) {
        if (this.len < newLen) {
            this.free = newLen - this.len;
            // 快取原有SDS字串
            char[] buffCopy = Arrays.copyOf(this.buff, this.len);
            // 新空間分配
            this.buff = new char[newLen];
            // 將快取的字串複製到SDS中
            this.buff = Arrays.copyOf(buffCopy, newLen);
        }

        return this;
    }

    // 將給定字元陣列拼接到SDS結尾處
    public SDS sdsCat(char[] s) {
        int lenCat = s.length;
        int lenNew = lenCat + this.len;
        // 如果SDS中的空閒不夠新的字元陣列儲存則需要擴充
        if (this.free < lenCat) {
            // 如果需要擴充的長度小於1M,
            // 按空間預分配的原則,程式分配和len屬性同樣大小的未使用空間,
            // 這時SDS len屬性的值將和free屬性的值相同
            if (lenNew < 1024) {
                set(sdsNewLen(lenNew * 2));
            }
            // 如果需要擴充的長度大於等於1M,
            // 按空間預分配的原則,程式分配1M的未使用空間
            else {
                set(sdsNewLen(lenNew + 1024));
            }
        }
        for (int i = 0; i < lenCat; i++) {
            this.buff[this.len + i] = s[i];
        }
        this.len = lenNew;
        this.free = this.buff.length -lenNew;
        return this;
    }

    @Override
    public String toString() {
        return "SDS{" +
                "len=" + len +
                ", free=" + free +
                ", buff=" + Arrays.toString(buff) +
                '}';
    }

    public static void main(String[] args) {
        SDS sds = new SDS();
        // 新建
        sds.sdsNew("redis".toCharArray());
        System.out.println("new: "+sds.toString());
        // 擴充
        sds.sdsNewLen(10);
        System.out.println("newLen to 10: "+sds.toString());
        // 追加
        String s1 = " welcome";
        sds.sdsCat(s1.toCharArray());
        System.out.println("append string: "+sds.toString());
        // 再次追加
        String s2 = "!!!";
        sds.sdsCat(s2.toCharArray());
        System.out.println("append string again: "+sds.toString());

    }
}

  • 在以上測試程式碼中我們通過自行編寫的api建立了一個SDS–“redis”, 並將此SDS擴充套件到10位長度,即已用長度5位,未用長度5位,在此基礎上進行拼接字串,觀察預分配機制是否實現,最後直接通過已預留的空間再次拼接字串,以下就是這個過程的輸出結果:
    在這裡插入圖片描述

三 、小結

  • 在一般程式中,如果修改字串長度的情況不太常出現,那麼每次修改都執行一次記憶體重分配是可以接受的。
  • Redis作為資料庫,經常被用於速度要求嚴苛、資料被頻繁修改的場合,如果每次修改字串的長度都需要執行一次記憶體重分配的話,那麼光是執行記憶體重分配的時間就會佔去修改字串所用時間的一大部分,如果這種修改頻繁地發生的話,可能還會對效能造成影響。
  • 簡單動態字串(SDS)的空間預分配機制正是Redis效能保證的一種有效手段。