Redis之intset數據結構


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進行保存數據,后面我們講到這幾個命令時會詳細講解,操作時使用的存儲策略。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM