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进行保存数据,后面我们讲到这几个命令时会详细讲解,操作时使用的存储策略。

posted @ 2015-10-28 21:48  蒲蜡  阅读(4672)  评论(0编辑  收藏  举报