Redis—數據結構之SDS

来源:http://www.cnblogs.com/zxiner/archive/2017/07/17/7197790.html
-Advertisement-
Play Games

Redis是一個Key Value資料庫。Redis有5種數據類型:字元串、列表、哈希、集合、有序集合。而字元串的底層實現方法之一就是使用sds。以下描述中請讀者註意區分sds是指簡單動態字元串這一數據結構(用大寫表示)還是sdshdr頭部中buf數組的起始地址(用小寫表示)。 SDS源碼 如下源碼 ...


Redis是一個Key Value資料庫。Redis有5種數據類型:字元串、列表、哈希、集合、有序集合。而字元串的底層實現方法之一就是使用sds。以下描述中請讀者註意區分sds是指簡單動態字元串這一數據結構(用大寫表示)還是sdshdr頭部中buf數組的起始地址(用小寫表示)。

SDS源碼

如下源碼所示。

根據要保存的字元串長度選用不同的頭部大小,從而節省記憶體,註意sdshdr5與其他不同,下麵會有介紹。

SDS由兩部分組成:sds、sdshdr。sds是一個char類型的指針,指向buf數組首元素,buf數組是存儲字元串的實際位置;sdshdr是SDS的頭部,為SDS加上一個頭部的好處就是為了提高某些地方的效率,比如獲取buf數組中字元串長度,用O(1)的複雜度從頭部就能取得。buf數組是一個空數組,從而使得sdshdr是一個可變長度的結構體,用一個空數組的好處就是分配記憶體時,只用分配一次,而且頭部所占用的記憶體和sds的記憶體是連續的,釋放時也只用釋放一次

sdshdr結構體中各欄位的介紹:len : 已存儲的字元串長度;alloc : 能存儲的字元串的最大容量,不包括SDS頭部和結尾的NULL字元;flags : 標誌位,低3位代表了sds頭部類型,高5位未用;buf[] : 字元數組,存儲字元串;註意sdshdr5沒有len和alloc欄位,其flags的低3位同樣代表頭部類型,但高5位代表保存的字元串長度。 __attribute__ ((__packed__)) : 使得編譯器不會因為記憶體對齊而在結構體中填充位元組,以保證記憶體的緊湊,這樣sds - 1就可以得到flags欄位,進而能夠得到其頭部類型。如果填充了位元組,則就不能得到flags欄位。

buf數組尾部隱含有一個'\0',SDS是以len欄位來判斷是否到達字元串末尾,而不是以'\0'判斷結尾。所以sds存儲的字元串中間可以出現'\0',即sds字元串是二進位安全的

typedef char *sds;

struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

 

既然有這麼多類型的頭部,一定會有類似巨集定義之類能夠標識頭部,的確有,如下所示:

// flags的低三位代表不同類型的sds頭部:
#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
#define SDS_TYPE_MASK 7
#define SDS_TYPE_BITS 3

 

SDS操作

因為sds和頭部是記憶體連續的,所以當我們得到了一個sds,只要將它-1就可得到flags欄位,減頭部大小即可得到頭部起始地址。SDS的很多操作就是利用了這一點,從而帶來了極大的方便和快速。下麵我們介紹幾個SDS比較重要的幾個操作

1. 獲取頭部起始地址

將sds減去頭部大小即可。非常方便快速。

// 返回一個指向sds頭部的起始地址的指針
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
// 返回sds頭部的起始地址
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))

2. 獲取buf數組中sds存儲的字元串長度

先後移1位,得到flags欄位,再和掩碼相與即可得到頭部類型。

static inline size_t sdslen(const sds s) {
    unsigned char flags = s[-1]; // 記憶體空間連續,所以往後移1個位元組,便是flags欄位
    switch(flags&SDS_TYPE_MASK) { // 和flags低3位相與,得到sds頭部類型
        case SDS_TYPE_5:
            return SDS_TYPE_5_LEN(flags);
        case SDS_TYPE_8:
            return SDS_HDR(8,s)->len; // 先移動到sds頭部的起始地址,進而可以直接獲取len欄位的值。下同
        case SDS_TYPE_16:
            return SDS_HDR(16,s)->len;
        case SDS_TYPE_32:
            return SDS_HDR(32,s)->len;
        case SDS_TYPE_64:
            return SDS_HDR(64,s)->len;
    }
    return 0;
}

3. 獲取buf數組中剩餘可用的記憶體大小

static inline size_t sdsavail(const sds s) {
    unsigned char flags = s[-1]; // 後移1位元組,得到flags欄位
    switch(flags&SDS_TYPE_MASK) { // 得到sds頭部類型
        case SDS_TYPE_5: {
            return 0;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            return sh->alloc - sh->len; // 總大小減去已使用大小
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            return sh->alloc - sh->len;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            return sh->alloc - sh->len;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            return sh->alloc - sh->len;
        }
    }
    return 0;
}

4. 使用字元串初始化一個SDS

註意分配時,程式會自動為buf數組最後一個元素後面添加上'\0','\0'對外部完全是透明的,分配記憶體時自動多分配1個位元組保存'\0',buf數組最後自動添加'\0'。

// sds尾部隱含有一個'\0';sds是以len欄位來判斷是否到達字元串末尾
// 所以sds存儲的字元串中間可以出現'\0',即sds字元串是二進位安全的

// 分配一個新sds,buf數組存儲內容init
sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;
    char type = sdsReqType(initlen); // 根據長度大小選擇合適的sds頭部
    /* Empty strings are usually created in order to append. Use type 8
     * since type 5 is not good at this. */
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    int hdrlen = sdsHdrSize(type); // 獲取sds頭部大小
    unsigned char *fp; /* flags pointer. */

    // 為sds分配記憶體,總大小為:頭部大小+存儲字元串的長度+末尾隱含的空字元大小
    sh = s_malloc(hdrlen+initlen+1); 
    if (!init)
        memset(sh, 0, hdrlen+initlen+1); // 記憶體初始化為0
    if (sh == NULL) return NULL;
    s = (char*)sh+hdrlen; // buf數組的起始地址
    fp = ((unsigned char*)s)-1; // 指向flags欄位
    // 初始化sds頭部的len,alloc,flags欄位
    switch(type) {
        case SDS_TYPE_5: {
            *fp = type | (initlen << SDS_TYPE_BITS);
            break;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
    }
    // 初始化buf數組
    if (initlen && init)
        memcpy(s, init, initlen); // 拷貝init到buf數組
    s[initlen] = '\0'; // 添加末尾的空字元
    return s;
}
View Code

5. 空間預分配

當需要將SDS的len增加addlen個位元組時,如果SDS剩餘空間足夠,則什麼都不用做。如果剩餘空間不夠,則會分配新的記憶體空間,並且採用預分配。新長度newlen為原len+addlen,若newlen小於1M,則為SDS分配新的記憶體大小為2*newlen;若newlen大於等於1M,則SDS分配新的記憶體大小為newlen  + 1M。

 // 為sds的len欄位增加addlen個位元組,剩餘空間不足時會引起空間重新分配
sds sdsMakeRoomFor(sds s, size_t addlen) {
    void *sh, *newsh;
    size_t avail = sdsavail(s);
    size_t len, newlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;

    /* Return ASAP if there is enough space left. */
    if (avail >= addlen) return s; // sds剩餘空間足夠

    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    newlen = (len+addlen); // sds剩餘空間不夠,新的len為len+addlen
    
    // 下麵兩步實現空間預分配
    if (newlen < SDS_MAX_PREALLOC) // 新長度小於1M,則len設為2*(len+addlen)大小
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC; // 新長度大於1M,則len設為 len+1M 大小

    type = sdsReqType(newlen); // 新len對應的sds頭部

    /* Don't use type 5: the user is appending to the string and type 5 is
     * not able to remember empty space, so sdsMakeRoomFor() must be called
     * at every appending operation. */
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;

    hdrlen = sdsHdrSize(type);
    if (oldtype==type) {
        newsh = s_realloc(sh, hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {
        /* Since the header size changes, need to move the string forward,
         * and can't use realloc */
        newsh = s_malloc(hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    }
    sdssetalloc(s, newlen);
    return s;
}
View Code

6. 惰性空間釋放

當要清空一個SDS時,並不真正釋放其記憶體,而是設置len欄位為0即可,這樣當之後再次使用到該SDS時,可避免重新分配記憶體,從而提高效率。

 // 清空sds內容,len欄位清為0
 // 但之前的空間並未釋放,可避免以後的重新分配記憶體。實現惰性空間釋放
void sdsclear(sds s) {
    sdssetlen(s, 0);
    s[0] = '\0';
}

 

只要理解了sds和sdshdr,其操作函數便很容易理解。剩下的就不一一介紹了,我在閱讀過程中也做了部分註釋,下麵附上源碼及註釋。SDS共兩個文件:sds.h和sds.c

sds.h :

/* SDSLib 2.0 -- A C dynamic strings library
 * 簡單動態字元串
 */

#ifndef __SDS_H
#define __SDS_H

#define SDS_MAX_PREALLOC (1024*1024) // 1M,空間預分配使用

#include <sys/types.h>
#include <stdarg.h>
#include <stdint.h>

// 指向存儲數據的起始地址
typedef char *sds;

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */

// sds由兩部分組成:sds頭部(即下麵的各種結構體)、真正存儲字元串的字元數組
// 這兩部分在記憶體上連續 
// len : 已存儲的字元串長度
// alloc : 能存儲的字元串的最大容量,不包括sds頭部和結尾的NULL字元
// flags : 標誌位,最低三位代表了sds頭部類型
// buf[] : 字元數組,存儲字元串
// __attribute__ ((__packed__)) : 
// 使得編譯器不會因為記憶體對齊而在結構體中填充位元組,以保證記憶體的緊湊,使得下麵的s[-1]得到正確的地址
// char buf[] : 初始時不占用記憶體,而且使得頭部記憶體和存儲字元串的記憶體地址連續。
// sdshdr5比較特殊,flags欄位低3位代表sds頭部類型,高5位代表已存儲的字元串長度
// 分為不同類型的頭部,目的是為了存儲不同長度的字元串使用不同類型,從而節省記憶體

 struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

// flags的低三位代表不同類型的sds頭部:
#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
#define SDS_TYPE_MASK 7
#define SDS_TYPE_BITS 3

// 返回一個指向sds頭部的起始地址的指針
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
// 返回sds頭部的起始地址
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
// 獲取sdshdr5類型的sds存儲的字元串長度
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)

// 獲取buf數組中sds存儲的字元串長度
static inline size_t sdslen(const sds s) {
    unsigned char flags = s[-1]; // 記憶體空間連續,所以往後移1個位元組,便是flags欄位
    switch(flags&SDS_TYPE_MASK) { // 和flags低3位相與,得到sds頭部類型
        case SDS_TYPE_5:
            return SDS_TYPE_5_LEN(flags);
        case SDS_TYPE_8:
            return SDS_HDR(8,s)->len; // 先移動到sds頭部的起始地址,進而可以直接獲取len欄位的值。下同
        case SDS_TYPE_16:
            return SDS_HDR(16,s)->len;
        case SDS_TYPE_32:
            return SDS_HDR(32,s)->len;
        case SDS_TYPE_64:
            return SDS_HDR(64,s)->len;
    }
    return 0;
}

// 獲取buf數組中剩餘可用的記憶體大小
static inline size_t sdsavail(const sds s) {
    unsigned char flags = s[-1]; // 後移1位元組,得到flags欄位
    switch(flags&SDS_TYPE_MASK) { // 得到sds頭部類型
        case SDS_TYPE_5: {
            return 0;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            return sh->alloc - sh->len; // 總大小減去已使用大小
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            return sh->alloc - sh->len;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            return sh->alloc - sh->len;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            return sh->alloc - sh->len;
        }
    }
    return 0;
}

// 設置sds頭部的len欄位
static inline void sdssetlen(sds s, size_t newlen) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            {
                // 對於sdshdr5,則是設置flags的高5位
                unsigned char *fp = ((unsigned char*)s)-1;
                *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
            }
            break;
        case SDS_TYPE_8:
            SDS_HDR(8,s)->len = newlen;
            break;
        case SDS_TYPE_16:
            SDS_HDR(16,s)->len = newlen;
            break;
        case SDS_TYPE_32:
            SDS_HDR(32,s)->len = newlen;
            break;
        case SDS_TYPE_64:
            SDS_HDR(64,s)->len = newlen;
            break;
    }
}

// 將sds頭部的len欄位增加inc
static inline void sdsinclen(sds s, size_t inc) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            {
                // 對於sdshdr5,則是設置flags的高5位
                unsigned char *fp = ((unsigned char*)s)-1;
                unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
                *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
            }
            break;
        case SDS_TYPE_8:
            SDS_HDR(8,s)->len += inc;
            break;
        case SDS_TYPE_16:
            SDS_HDR(16,s)->len += inc;
            break;
        case SDS_TYPE_32:
            SDS_HDR(32,s)->len += inc;
            break;
        case SDS_TYPE_64:
            SDS_HDR(64,s)->len += inc;
            break;
    }
}

/* sdsalloc() = sdsavail() + sdslen() */
// 獲取sds的buf數組總的大小
static inline size_t sdsalloc(const sds s) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            return SDS_TYPE_5_LEN(flags);
        case SDS_TYPE_8:
            return SDS_HDR(8,s)->alloc;
        case SDS_TYPE_16:
            return SDS_HDR(16,s)->alloc;
        case SDS_TYPE_32:
            return SDS_HDR(32,s)->alloc;
        case SDS_TYPE_64:
            return SDS_HDR(64,s)->alloc;
    }
    return 0;
}

// 設置sds的buf數組總的大小
static inline void sdssetalloc(sds s, size_t newlen) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            /* Nothing to do, this type has no total allocation info. */
            break;
        case SDS_TYPE_8:
            SDS_HDR(8,s)->alloc = newlen;
            break;
        case SDS_TYPE_16:
            SDS_HDR(16,s)->alloc = newlen;
            break;
        case SDS_TYPE_32:
            SDS_HDR(32,s)->alloc = newlen;
            break;
        case SDS_TYPE_64:
            SDS_HDR(64,s)->alloc = newlen;
            break;
    }
}

sds sdsnewlen(const void *init, size_t initlen);
sds sdsnew(const char *init);
sds sdsempty(void);
sds sdsdup(const sds s);
void sdsfree(sds s);
sds sdsgrowzero(sds s, size_t len);
sds sdscatlen(sds s, const void *t, size_t len);
sds sdscat(sds s, const char *t);
sds sdscatsds(sds s, const sds t);
sds sdscpylen(sds s, const char *t, size_t len);
sds sdscpy(sds s, const char *t);

sds sdscatvprintf(sds s, const char *fmt, va_list ap);
#ifdef __GNUC__
sds sdscatprintf(sds s, const char *fmt, ...)
    __attribute__((format(printf, 2, 3)));
#else
sds sdscatprintf(sds s, const char *fmt, ...);
#endif

sds sdscatfmt(sds s, char const *fmt, ...);
sds sdstrim(sds s, const char *cset);
void sdsrange(sds s, int start, int end);
void sdsupdatelen(sds s);
void sdsclear(sds s);
int sdscmp(const sds s1, const sds s2);
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
void sdsfreesplitres(sds *tokens, int count);
void sdstolower(sds s);
void sdstoupper(sds s);
sds sdsfromlonglong(long long value);
sds sdscatrepr(sds s, const char *p, size_t len);
sds *sdssplitargs(const char *line, int *argc);
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
sds sdsjoin(char **argv, int argc, char *sep);
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);

/* Low level functions exposed to the user API */
sds sdsMakeRoomFor(sds s, size_t addlen);
void sdsIncrLen(sds s, int incr);
sds sdsRemoveFreeSpace(sds s);
size_t sdsAllocSize(sds s);
void *sdsAllocPtr(sds s);

/* Export the allocator used by SDS to the program using SDS.
 * Sometimes the program SDS is linked to, may use a different set of
 * allocators, but may want to allocate or free things that SDS will
 * respectively free or allocate. */
void *sds_malloc(size_t size);
void *sds_realloc(void *ptr, size_t size);
void sds_free(void *ptr);

#ifdef REDIS_TEST
int sdsTest(int argc, char *argv[]);
#endif

#endif
View Code

sds.c :

/* SDSLib 2.0 -- A C dynamic strings library
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include "sds.h"
#include "sdsalloc.h"

// 獲取type類型的sds對應的頭部類型大小
static inline int sdsHdrSize(char type) {
    switch(type&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            return sizeof(struct sdshdr5);
        case SDS_TYPE_8:
            return sizeof(struct sdshdr8);
        case SDS_TYPE_16:
            return sizeof(struct sdshdr16);
        case SDS_TYPE_32:
            return sizeof(struct sdshdr32);
        case SDS_TYPE_64:
            return sizeof(struct sdshdr64);
    }
    return 0;
}

// 根據不同大小選用不同類型的sds頭部
static inline char sdsReqType(size_t string_size) {
    if (string_size < 1<<5) // 0~2^5-1
        return SDS_TYPE_5;
    if (string_size < 1<<8) // 2^5~2^8-1
        return SDS_TYPE_8;
    if (string_size < 1<<16) // 2^8~2^16-1
        return SDS_TYPE_16;
    if (string_size < 1<<32) // 2^16~2^32-1
        return SDS_TYPE_32;
    return SDS_TYPE_64; // 2^32~
}

/* Create a new sds string with the content specified by the 'init' pointer
 * and 'initlen'.
 * If NULL is used for 'init' the string is initialized with zero bytes.
 *
 * The string is always null-termined (all the sds strings are, always) so
 * even if you create an sds string with:
 *
 * mystring = sdsnewlen("abc",3);
 *
 * You can print the string with printf() as there is an implicit \0 at the
 * end of the string. However the string is binary safe and can contain
 * \0 characters in the middle, as the length is stored in the sds header. */
 
// sds尾部隱含有一個'\0';sds是以len欄位來判斷是否到達字元串末尾
// 所以sds存儲的字元串中間可以出現'\0',即sds字元串是二進位安全的

// 分配一個新sds,buf數組存儲內容init
sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;
    char type = sdsReqType(initlen); // 根據長度大小選擇合適的sds頭部
    /* Empty strings are usually created in order to append. Use type 8
     * since type 5 is not good at this. */
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    int hdrlen = sdsHdrSize(type); // 獲取sds頭部大小
    unsigned char *fp; /* flags pointer. */

    // 為sds分配記憶體,總大小為:頭部大小+存儲字元串的長度+末尾隱含的空字元大小
    sh = s_malloc(hdrlen+initlen+1); 
    if (!init)
        memset(sh, 0, hdrlen+initlen+1); // 記憶體初始化為0
    if (sh == NULL) return NULL;
    s = (char*)sh+hdrlen; // buf數組的起始地址
    fp = ((unsigned char*)s)-1; // 指向flags欄位
    // 初始化sds頭部的len,alloc,flags欄位
    switch(type) {
        case SDS_TYPE_5: {
            *fp = type | (initlen << SDS_TYPE_BITS);
            break;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            sh

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 故障說明: 遠程調整實例記憶體時疏忽,將實例最大記憶體調整為了0,因此最大記憶體變成了128MB的最小值。 解決方式: 1.正常關閉SQL Server服務,如果是集群,需要先關停止集群角色防止故障轉移,然後再單獨關閉服務。 --註意這一步可能會使實例處於掛起狀態很長時間,但是正常關閉是必須的,直接殺進程 ...
  • 早上打開筆記本想開啟SQL Server服務時報錯,於是根據提示查看windows日誌: 依次點開報錯發現第一條是1433埠被占用,於是找相關的進程: 於是殺掉此進程: 然後啟動SQL Server服務成功。 ...
  • 1、string(key:value類型) 2、hash(name {key1:value1,key2:value2,....}) 3、List(隊列,兩邊均可以取值) 4、set(集合,不重覆數據的集) 5、Sorted set(有序集合,帶權重) ...
  • 這裡整理5個Spark的應用實例,希望對Spark學習者能夠有所幫助 ...
  • 1.KILL掉系統里的MySQL進程; killall -TERM mysqld 2.用以下命令啟動MySQL,以不檢查許可權的方式啟動; mysqld --skip-grant-tables & 3.然後用空密碼方式使用root用戶登錄 MySQL; MySQL -u root 4.修改root用戶 ...
  • 開啟回收站RECYCLEBIN=ON,預設開啟ALTER SYSTEM SET RECYCLEBIN=OFF SCOPE=SPFILE;一、從回收站還原表還原刪除的表和從屬對象。如果多個回收站條目具有相同原始名稱,則:使用系統生成的唯一名稱來還原特定版本使用原始名稱時,還原的表遵循後進先出(LIFO... ...
  • 在資料庫開發過程中,字元串和關係表的轉化是一項基本技能。當字元串中存在分隔符時,有時將其轉換成關係表數據,和其他數據表進行join查詢,出現這種情況,是因為沒有遵守關係資料庫的設計範式,沒有把字元串拆分成原子項存儲,也有可能是數據傳參數;有時會遇到相反的情況,需要將關係表的相關數據拼接成一個字元串顯 ...
  • `React Native iOS RN`是使用腳本語言編寫的,實現了“解釋執行”的方式,而這種執行方式的修改只需替換腳步即可,不需要重新發佈程式,熱更新的方式極大的方便了迭代開發。 今天我們選擇的熱更新組件是 ,這是國內開發的,功能類似 (`CodePush Pushy`支持增量更新,最大化的降低 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...