0.前言
redis中intset是一個整數集合, 只能存儲整數類型的數據, 可以是16位, 32位, 或者是64位, 是以升序排列的數組進行保存數據,下面會介紹具體數據結構和對其操作過程.
1.數據結構定義
typedef struct intset {
/*編碼*/
uint32_t encoding;
/*長度*/
uint32_t length;
/*集合內容,按升序排列數組*/
int8_t contents[];
} intset;
2.創建集合
創建集合需要分配下內存空間, 初始化結構體內變量
intset *intsetNew(void) {
intset *is = zmalloc(sizeof(intset));
is->encoding = intrev32ifbe(INTSET_ENC_INT16);
is->length = 0;
return is;
}
3.添加元素
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
/*為了節省空間, 判斷添加的元素需要編碼為何種數據類型, 比如int16, int32, int64*/
uint8_t valenc = _intsetValueEncoding(value);
uint32_t pos;
if (success) *success = 1;
/*如果intset編碼位數無法容納新元素,則需要重新更新整個intset編碼*/
if (valenc > intrev32ifbe(is->encoding)) {
/* 更新編碼並添加新元素 */
return intsetUpgradeAndAdd(is,value);
} else {
/*搜索新添加元素是否已經存在,存在則返回失敗,此函數在查找一節會詳細講解*/
if (intsetSearch(is,value,&pos)) {
if (success) *success = 0;
return is;
}
/*擴展內存空間*/
is = intsetResize(is,intrev32ifbe(is->length)+1);
if (pos < intrev32ifbe(is->length))
/*如果添加元素位置不是一整塊內存尾部,則需將其后面元素后移一個元素位置*/
intsetMoveTail(is,pos,pos+1);
}
/*pos位置處賦值*/
_intsetSet(is,pos,value);
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
return is;
}
/*根據元素大小決定元素存儲長度*/
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;
}
/*重置intset空間大小,每次zrealloc擴展內存大小*/
static intset *intsetResize(intset *is, uint32_t len) {
uint32_t size = len*intrev32ifbe(is->encoding);
is = zrealloc(is,sizeof(intset)+size);
return is;
}
/*向后移動元素*/
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);
}
/* 更新集合編碼並添加新元素 */
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
uint8_t curenc = intrev32ifbe(is->encoding);
uint8_t newenc = _intsetValueEncoding(value);
int length = intrev32ifbe(is->length);
int prepend = value < 0 ? 1 : 0;
/* 設置新編碼,並擴展足夠內存空間*/
is->encoding = intrev32ifbe(newenc);
is = intsetResize(is,intrev32ifbe(is->length)+1);
/* 取出原來空間中元素,從后開始往前依次放入新的位置 */
while(length--)
_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
/* 放置value值,要么在數組頭,要么在數組尾部 */
if (prepend)
_intsetSet(is,0,value);
else
_intsetSet(is,intrev32ifbe(is->length),value);
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
return is;
}
4.查找元素
查找元素依靠intsetFind函數進行,調用intsetSearch進行實際查找
uint8_t intsetFind(intset *is, int64_t value) {
/*判斷待查元素編碼是否符合條件,不符合直接返回false,否則進入intsetSearch進行實際查找*/
uint8_t valenc = _intsetValueEncoding(value);
return valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,NULL);
}
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;
/* 集合為空,直接返回第一個位置 */
if (intrev32ifbe(is->length) == 0) {
if (pos) *pos = 0;
return 0;
} else {
/* _intsetGet函數僅僅獲取set集合中pos位置的值, 如果待查元素大於集合尾部元素,則直接返回待查元素位置為集合長度*/
if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) {
if (pos) *pos = intrev32ifbe(is->length);
return 0;
/*如果待查元素小於集合頭部元素,則直接返回待查元素位置為0*/
} else if (value < _intsetGet(is,0)) {
if (pos) *pos = 0;
return 0;
}
}
/*二分查找*/
while(max >= min) {
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;
}
}
/*找到元素返回1,否則返回0,pos為元素應該位置*/
if (value == cur) {
if (pos) *pos = mid;
return 1;
} else {
if (pos) *pos = min;
return 0;
}
}
5.刪除元素
intset *intsetRemove(intset *is, int64_t value, int *success) {
uint8_t valenc = _intsetValueEncoding(value);
uint32_t pos;
if (success) *success = 0;
/*查找元素是否存在*/
if (valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,&pos)) {
uint32_t len = intrev32ifbe(is->length);
if (success) *success = 1;
/*刪除元素,並移動其他元素覆蓋原來位置,這里沒有緩存空間,而是直接重置原來空間,可能是為了節省內存*/
if (pos < (len-1)) intsetMoveTail(is,pos+1,pos);
is = intsetResize(is,len-1);
is->length = intrev32ifbe(len-1);
}
return is;
}
總結
intset實質就是一個有序數組,可以看到添加刪除元素都比較耗時,查找元素是O(logN)時間復雜度,不適合大規模的數據。我們在進行sadd,sdel等對無序集合進行操作時,並不是一定使用intset進行保存數據,后面我們講到這幾個命令時會詳細講解,操作時使用的存儲策略。