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

  • 相关阅读:
    Windows使用SCHTASKS 命令执行定时任务
    window10设置定时任务
    uiautomator2+python自动化测试1-环境准备
    uiautomator2+python自动化测试2-查看app页面元素利器weditor
    APPIUM 自带的webdriveragent
    使用 mitmproxy + python 做拦截代理
    mitmproxy 实战
    深入学习mitmproxy
    将博客搬至CSDN
    CS231N Assignment5 图像分类练习
  • 原文地址:https://www.cnblogs.com/ourroad/p/4892945.html
Copyright © 2011-2022 走看看