《Redis設計與實現》數組
整數集合(intset)是Redis中集合鍵的底層實現之一,當一個集合只包含整數值元素,而且這個集合的元素很少時,Redis就會使用整數集合做爲集合鍵的底層實現。app
在Redis中,它能夠保存類型爲int16_t
、int32_t
或int64_t
的的整數值,而且保證集合中不會出現重複元素。dom
// intset.h typedef struct intset { uint32_t encoding; // 編碼方式 uint32_t length; // 集合中的元素數量 int8_t contents[]; // 集合中保存元素的數組 } intset;
encoding
: 有3種屬性值INTSET_ENC_INT16
、INTSET_ENC_INT32
、INTSET_ENC_INT64
用於表示保存在contents
中的元素真正的類型:
INTSET_ENC_INT16
: 表示contents
中保存的是int16_t
類型的整數值(-32768~32767)。INTSET_ENC_INT32
: 表示contents
中保存的是int32_t
類型的整數值(-2147483648~2147483647)。INTSET_ENC_INT64
: 表示contents
中保存的是int64_t
類型的整數值(-9223372036854775808~9223372036854775807)。length
: 表示的是集合中元素的數量,但不是contents
中int8_t
的長度。contents
: 集合中的元素是按值得大小,從小到大有序的保存在content
中,而且不包含任何重複的項目;若是encoding
是INTSET_ENC_INT64
,一個元素在contents
中將會佔8個int8_t
的大小。《Redis設計與實現》ide
當將一個新的元素加入到整數集合中,並且新的元素類型比整數集合現有的元素類型encoding
要長,整數集合須要進行升級,擴展數組空間並將原有的元素都轉換成更長的類型。函數
升級整數集合的順序:ui
升級的優勢:this
Redis中,整數集合不支持降級操做,因此一旦對數組進行了升級,編碼會一直保持升級後的狀態。編碼
intset *intsetNew(void)
建立一個新的整數集合:spa
/* Create an empty intset. */ intset *intsetNew(void) { // 分配內存 intset *is = zmalloc(sizeof(intset)); // 默認 int16_t, 將 INTSET_ENC_INT16 轉爲 int32_t 格式保存到 encoding 中 is->encoding = intrev32ifbe(INTSET_ENC_INT16); // 初始化長度 0 is->length = 0; return is; }
intset *intsetAdd(intset *is, int64_t value, uint8_t *success)
給定元素value
添加到整數集合is
中,成功或者失敗的標記將會傳入success
中:設計
/* Insert an integer in the intset */ intset *intsetAdd(intset *is, int64_t value, uint8_t *success) { // 經過_intsetValueEncoding()函數,判斷value須要哪一個整數的類型,並將類型傳回valenc中 uint8_t valenc = _intsetValueEncoding(value); uint32_t pos; // 將成功標誌默認設爲 1 if (success) *success = 1; /* Upgrade encoding if necessary. If we need to upgrade, we know that * this value should be either appended (if > 0) or prepended (if < 0), * because it lies outside the range of existing values. */ // 判斷是否須要對整數集合進行升級 if (valenc > intrev32ifbe(is->encoding)) { /* This always succeeds, so we don't need to curry *success. */ // 執行升級並將值value添加進整數集合 // 而後返回結果 return intsetUpgradeAndAdd(is,value); } else { /* Abort if the value is already present in the set. * This call will populate "pos" with the right position to insert * the value when it cannot be found. */ // 在整數集合中查找值value是否已經存在 // 若是value不存在於 整數集合is 中,pos的值將是值value適合插入的位置 // 若是已經存在,將 *success 置爲1,並返回 if (intsetSearch(is,value,&pos)) { if (success) *success = 0; return is; } // 對 整數集合is 的內存空間進行擴展,添加一個元素的空間大小 is = intsetResize(is,intrev32ifbe(is->length)+1); // 若是插入位置不是數組尾,則經過intsetMoveTail()調理數組中插入位置後的元素的位置 if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1); } // 執行插入函數,將值value插入is的pos位置 _intsetSet(is,pos,value); // 長度加1 is->length = intrev32ifbe(intrev32ifbe(is->length)+1); return is; } /* Return the required encoding for the provided value. */ // 判斷傳入的值大小是須要int16_t、int32_t 仍是 int64_t // 並返回所須要的類型標識 static uint8_t _intsetValueEncoding(int64_t v) { if (v < INT32_MIN || v > INT32_MAX) return INTSET_ENC_INT64; else if (v < INT16_MIN || v > INT16_MAX) return INTSET_ENC_INT32; else return INTSET_ENC_INT16; } /* Upgrades the intset to a larger encoding and inserts the given integer. */ // 升級 並 將 value 插入 整數集合is 中 static intset *intsetUpgradeAndAdd(intset *is, int64_t value) { // 當前集合中整數的類型 uint8_t curenc = intrev32ifbe(is->encoding); // 集合中整數將要升級的類型 uint8_t newenc = _intsetValueEncoding(value); // 集合中元素的個數 int length = intrev32ifbe(is->length); // 新插入的值value是否爲負數 int prepend = value < 0 ? 1 : 0; /* First set new encoding and resize */ // 更新 整數集合is 中的整數類型 編碼 is->encoding = intrev32ifbe(newenc); // 爲is中的數組從新分配大小 // 大小 = 新整數類型的大小 * (is中本來元素的個數 + 1) is = intsetResize(is,intrev32ifbe(is->length)+1); /* Upgrade back-to-front so we don't overwrite values. * Note that the "prepend" variable is used to make sure we have an empty * space at either the beginning or the end of the intset. */ // 從後往前調整數組中的元素,這樣就不會修改元素原本的順序 // 由於插入了value,而致使了 整數集合須要升級,則說明value要麼是最大的正數,要麼是最小的負數 // 因此若是是負數,則數組中元素不只要調整結構,還要日後移位,空出第一個空位 // 若是是正數,則數組中元素只須要調整結構 while(length--) // _intsetGetEncoded()是經過元素的索引和編碼獲取元素的值 _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc)); /* Set the value at the beginning or the end. */ if (prepend) // 若是是負數,插入第一位 _intsetSet(is,0,value); else // 若是是正數,插入最後一位 _intsetSet(is,intrev32ifbe(is->length),value); // 長度加1 is->length = intrev32ifbe(intrev32ifbe(is->length)+1); return is; } /* Return the value at pos, given an encoding. */ static int64_t _intsetGetEncoded(intset *is, int pos, uint8_t enc) { // 經過傳入的索引 pos 和 整數結構編碼 enc 在 is 中獲取元素的值 int64_t v64; int32_t v32; int16_t v16; if (enc == INTSET_ENC_INT64) { memcpy(&v64,((int64_t*)is->contents)+pos,sizeof(v64)); memrev64ifbe(&v64); return v64; } else if (enc == INTSET_ENC_INT32) { memcpy(&v32,((int32_t*)is->contents)+pos,sizeof(v32)); memrev32ifbe(&v32); return v32; } else { memcpy(&v16,((int16_t*)is->contents)+pos,sizeof(v16)); memrev16ifbe(&v16); return v16; } } /* Search for the position of "value". Return 1 when the value was found and * sets "pos" to the position of the value within the intset. Return 0 when * the value is not present in the intset and sets "pos" to the position * where "value" can be inserted. */ // 在 is 中查找 value,若是存在則返回1,pos爲值所在的位置 // 若是值不存在,則返回0,pos爲值value可插入的位置 static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) { int min = 0, max = intrev32ifbe(is->length)-1, mid = -1; int64_t cur = -1; /* The value can never be found when the set is empty */ if (intrev32ifbe(is->length) == 0) { if (pos) *pos = 0; return 0; } else { /* Check for the case where we know we cannot find the value, * but do know the insert position. */ if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) { // 判斷值value是否大於集合is中的最大值 if (pos) *pos = intrev32ifbe(is->length); return 0; } else if (value < _intsetGet(is,0)) { // 判斷值value是否小於集合is中的最小值 if (pos) *pos = 0; return 0; } } // 二分查找法 查找value的位置或者適合插入的位置 while(max >= min) { // 經過 >> 1 進行除2操做 mid = ((unsigned int)min + (unsigned int)max) >> 1; cur = _intsetGet(is,mid); if (value > cur) { min = mid+1; } else if (value < cur) { max = mid-1; } else { break; } } if (value == cur) { if (pos) *pos = mid; return 1; } else { if (pos) *pos = min; return 0; } } /* Resize the intset */ // 爲整數集合is擴展數組空間,len爲指定的元素個數 static intset *intsetResize(intset *is, uint32_t len) { uint32_t size = len*intrev32ifbe(is->encoding); is = zrealloc(is,sizeof(intset)+size); return is; } // 在整數集合is中 從指定位置 from開始 拷貝 數據 到 to 位置 static void intsetMoveTail(intset *is, uint32_t from, uint32_t to) { void *src, *dst; uint32_t bytes = intrev32ifbe(is->length)-from; uint32_t encoding = intrev32ifbe(is->encoding); if (encoding == INTSET_ENC_INT64) { src = (int64_t*)is->contents+from; dst = (int64_t*)is->contents+to; bytes *= sizeof(int64_t); } else if (encoding == INTSET_ENC_INT32) { src = (int32_t*)is->contents+from; dst = (int32_t*)is->contents+to; bytes *= sizeof(int32_t); } else { src = (int16_t*)is->contents+from; dst = (int16_t*)is->contents+to; bytes *= sizeof(int16_t); } memmove(dst,src,bytes); } /* Set the value at pos, using the configured encoding. */ // 在is指定位置pos中將值修改成value static void _intsetSet(intset *is, int pos, int64_t value) { uint32_t encoding = intrev32ifbe(is->encoding); if (encoding == INTSET_ENC_INT64) { ((int64_t*)is->contents)[pos] = value; memrev64ifbe(((int64_t*)is->contents)+pos); } else if (encoding == INTSET_ENC_INT32) { ((int32_t*)is->contents)[pos] = value; memrev32ifbe(((int32_t*)is->contents)+pos); } else { ((int16_t*)is->contents)[pos] = value; memrev16ifbe(((int16_t*)is->contents)+pos); } }
intset *intsetRemove(intset *is, int64_t value, int *success)
將值value
從整數集合*is
中刪除,若是成功,則*success
爲1,不然爲0:
/* Delete integer from intset */ intset *intsetRemove(intset *is, int64_t value, int *success) { uint8_t valenc = _intsetValueEncoding(value); uint32_t pos; if (success) *success = 0; // 判斷值是否在整數編碼的範圍內,若是是的話,調用intsetSearch()查找出 value 的位置 if (valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,&pos)) { // value 存在,而且位置在 pos uint32_t len = intrev32ifbe(is->length); /* We know we can delete */ if (success) *success = 1; /* Overwrite value with tail and update length */ // 若是value不是在數組末位,則將pos+1位置後的數據往前移1位,覆蓋掉本來值value if (pos < (len-1)) intsetMoveTail(is,pos+1,pos); // 釋放掉多出一位的內存空間 is = intsetResize(is,len-1); // 長度減1 is->length = intrev32ifbe(len-1); } return is; }
參考之《Redis設計與實現》
函數 | 做用 |
---|---|
intset *intsetNew(void) |
建立一個新的整數集合 |
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) |
將給定元素value 添加到整數集合is 中,成功或者失敗的標記將會傳入success 中 |
intset *intsetRemove(intset *is, int64_t value, int *success) |
從整數集合is 中移除給定元素value ,成功或者失敗的標記將會傳入success 中 |
uint8_t intsetFind(intset *is, int64_t value) |
檢查給定元素value 是否在整數集合is 中 |
int64_t intsetRandom(intset *is) |
從整數集合is 中隨機返回一個元素 |
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value) |
取出整數集合is 底層數組中在給定索引pos 上的元素並寫入value 中,若是給定的索引pos 超出數組長度,函數返回0,不然返回1 |
uint32_t intsetLen(const intset *is) |
返回整數集合is 中的元素個數 |
size_t intsetBlobLen(intset *is) |
返回整數集合is 中佔用的內存字節數 |