Redis的ziplist數據結構筆記

OBJ_ENCODING_ZIPLIST

ziplist也就是壓縮列表,是Redis爲了節約內存而開發的,是由一系列特殊編碼的連續內存塊組成的順序型數據結構,在Redis2.8的時候是做爲小數據量的列表底層實現。ziplist自己並無定義結構體,下面是《redis設計與實現》裏的說明圖。程序員

ziplist entry

下面是ziplistentry邏輯定義,並非實際的編碼,可是能夠用來理解entry是怎麼組成的。redis

/* We use this function to receive information about a ziplist entry. * Note that this is not how the data is actually encoded, is just what we * get filled by a function in order to operate more easily. */
typedef struct zlentry {
    unsigned int prevrawlensize; // 存儲上一個元素的長度數值所須要的字節數
    unsigned int prevrawlen;     // 前一個元素的長度
    unsigned int lensize;        // 存儲元素的長度數值所須要的字節數,能夠是一、2或者5字節,整數老是使用一個字節
    unsigned int len;            // 表示元素的長度
    unsigned int headersize;     /* prevrawlensize + lensize. */
    unsigned char encoding;      // 標記是字節數組仍是整數
    unsigned char *p;            // 壓縮鏈表以字符串的形式保存,該指針指向當前元素起始位置
} zlentry;
複製代碼

一個entry的數據組成以下圖所示:數組

一個entry的第一部分是prevrawlen,編碼了前一個元素的長度;entry的第二部分是encoding,編碼了元素自身的長度和類型;entry的第三部分是value,存放了元素自己,有些entry並無這一部分。數據結構

prevrawlen

每一個zlentry都存儲了前一個元素的長度,用字段prevrawlen來表示。ui

經過下面的源碼能夠知道在上一個元素小於254字節時,prevlensize等於1字節,不然prevlensize等於5字節。當prevlensize等於5字節時,prevrawlen的第一個字節會被設置爲0xFE,後面的四個字節纔是前一個元素的長度。this

#define ZIP_BIG_PREVLEN 254 /* Max number of bytes of the previous entry, for the "prevlen" field prefixing each entry, to be represented with just a single byte. Otherwise it is represented as FF AA BB CC DD, where AA BB CC DD are a 4 bytes unsigned integer representing the previous entry len. */

/* Return the number of bytes used to encode the length of the previous * entry. The length is returned by setting the var 'prevlensize'. */
#define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do { 
    if ((ptr)[0] < ZIP_BIG_PREVLEN) {                                          
        (prevlensize) = 1;                                                     
    } else {                                                                   
        (prevlensize) = 5;                                                     
    }                                                                          
} while(0);
複製代碼

encoding

在處理完prevrawlen字段,接下來就是處理encoding字段了。encoding字段的前兩個bit用來標識類型,若是元素是一個整數的話encoding字段固定爲一個字節,字節前兩個bit固定爲11。而後判斷元素的大小進而將整數範圍存儲在encoding字段的後6個bit裏。其中特殊的是當元素大於等於0且小於等於12的時候,元素會直接被寫入encoding字段中,就沒有後面的value字段了。編碼

/* Check if string pointed to by 'entry' can be encoded as an integer. * Stores the integer value in 'v' and its encoding in 'encoding'. */
int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding) {
    long long value;

    if (entrylen >= 32 || entrylen == 0) return 0;
    if (string2ll((char*)entry,entrylen,&value)) {
        /* Great, the string can be encoded. Check what's the smallest * of our encoding types that can hold this value. */
        if (value >= 0 && value <= 12) {
            *encoding = ZIP_INT_IMM_MIN+value;
        } else if (value >= INT8_MIN && value <= INT8_MAX) {
            *encoding = ZIP_INT_8B;
        } else if (value >= INT16_MIN && value <= INT16_MAX) {
            *encoding = ZIP_INT_16B;
        } else if (value >= INT24_MIN && value <= INT24_MAX) {
            *encoding = ZIP_INT_24B;
        } else if (value >= INT32_MIN && value <= INT32_MAX) {
            *encoding = ZIP_INT_32B;
        } else {
            *encoding = ZIP_INT_64B;
        }
        *v = value;
        return 1;
    }
    return 0;
}
複製代碼

當元素是一個字符串時,一樣會根據元素的長度設置encoding字段:spa

  • 當元素小於等於63字節時,encoding字段爲1字節,前兩位bit爲00,後6位用於存儲元素長度
  • 當元素小於等於16383字節時,encoding字段爲2字節,前兩位bit爲01,其他位用於存儲元素長度
  • 當元素小於等於4294967295字節時,encoding字段爲5字節,第1個字節前兩位bit爲10,後4個字節用於存儲元素長度
/* Write the encoidng header of the entry in 'p'. If p is NULL it just returns * the amount of bytes required to encode such a length. Arguments: * * 'encoding' is the encoding we are using for the entry. It could be * ZIP_INT_* or ZIP_STR_* or between ZIP_INT_IMM_MIN and ZIP_INT_IMM_MAX * for single-byte small immediate integers. * * 'rawlen' is only used for ZIP_STR_* encodings and is the length of the * srting that this entry represents. * * The function returns the number of bytes used by the encoding/length * header stored in 'p'. */
unsigned int zipStoreEntryEncoding(unsigned char *p, unsigned char encoding, unsigned int rawlen) {
    unsigned char len = 1, buf[5];

    if (ZIP_IS_STR(encoding)) {
        /* Although encoding is given it may not be set for strings, * so we determine it here using the raw length. */
        if (rawlen <= 0x3f) {
            if (!p) return len;
            buf[0] = ZIP_STR_06B | rawlen;
        } else if (rawlen <= 0x3fff) {
            len += 1;
            if (!p) return len;
            buf[0] = ZIP_STR_14B | ((rawlen >> 8) & 0x3f);
            buf[1] = rawlen & 0xff;
        } else {
            len += 4;
            if (!p) return len;
            buf[0] = ZIP_STR_32B;
            buf[1] = (rawlen >> 24) & 0xff;
            buf[2] = (rawlen >> 16) & 0xff;
            buf[3] = (rawlen >> 8) & 0xff;
            buf[4] = rawlen & 0xff;
        }
    } else {
        /* Implies integer encoding, so length is always 1. */
        if (!p) return len;
        buf[0] = encoding;
    }
    /* Store this length at p. */
    memcpy(p,buf,len);
    return len;
}
複製代碼

value

value字段就很是的簡單了,直接把元素放在encoding字段後面就能夠了,它能夠是一個整數或者是字節數組,元素的長度和類型都已經存儲在encoding字段裏了。設計

總結

能夠看到ziplist的實現是很是蛋疼的,Redis爲了節省一些內存也是喪心病狂。不過這樣內存是節省了,卻增長了cpu的負擔,redis也只是在數據量比較少的場景纔會使用這種數據結構。做爲一個Java程序員看到這樣的數據結構實際上是比較奇怪的,仍是ArrayList好用:)指針

參考資料

《redis設計與實現》

相關文章
相關標籤/搜索