ziplist
也就是壓縮列表,是Redis
爲了節約內存而開發的,是由一系列特殊編碼的連續內存塊組成的順序型數據結構,在Redis2.8
的時候是做爲小數據量的列表底層實現。ziplist
自己並無定義結構體,下面是《redis設計與實現》裏的說明圖。程序員
下面是ziplist
的entry
邏輯定義,並非實際的編碼,可是能夠用來理解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
並無這一部分。數據結構
每一個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);
複製代碼
在處理完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
encoding
字段爲1字節,前兩位bit
爲00,後6位用於存儲元素長度encoding
字段爲2字節,前兩位bit
爲01,其他位用於存儲元素長度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
字段就很是的簡單了,直接把元素放在encoding
字段後面就能夠了,它能夠是一個整數或者是字節數組,元素的長度和類型都已經存儲在encoding
字段裏了。設計
能夠看到ziplist
的實現是很是蛋疼的,Redis
爲了節省一些內存也是喪心病狂。不過這樣內存是節省了,卻增長了cpu
的負擔,redis
也只是在數據量比較少的場景纔會使用這種數據結構。做爲一個Java
程序員看到這樣的數據結構實際上是比較奇怪的,仍是ArrayList
好用:)指針