博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Redis源代码分析-内存数据结构intset
阅读量:6923 次
发布时间:2019-06-27

本文共 6392 字,大约阅读时间需要 21 分钟。

这次研究了一下intset。研究的过程中,一度看不下过去,可是还是咬牙挺过来了。看懂了也就是那么回事。静下心来,切莫浮躁

Redis为了追求高效,在存储下做了非常多的优化,像intset就是作者为了节约内存定制的数据结构,包含后面将要阅读的压缩列表。

intset是一个有序的整数集,提供了添加,删除,查找的接口,针对uint16_t uint32_t uint64_t,提供了不同编码的转换(严格的说仅仅是类型的提升)

首先。看一下它的结构定义:

typedef struct intset {                                                                                                                                                    uint32_t encoding;                                                                                                                                                     uint32_t length;                                                                                                                                                                                     int8_t contents[];                                                                                                                          } intset;
encoding:有例如以下几种编码

#define INTSET_ENC_INT16 (sizeof(int16_t))#define INTSET_ENC_INT32 (sizeof(int32_t))#define INTSET_ENC_INT64 (sizeof(int64_t))
实际上这里使用一个uint8_t存储就够了

length:当前整数集有多少个整数

contents[]:详细存储的位置。这里以一个字节为存储单元,方便对高类型进行寻址

看一下它对外提供的接口:

intset *intsetNew(void); intset *intsetAdd(intset *is, int64_t value, uint8_t *success);                                         intset *intsetRemove(intset *is, int64_t value, int *success);                                          uint8_t intsetFind(intset *is, int64_t value);                                                          int64_t intsetRandom(intset *is);uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);                                            uint32_t intsetLen(intset *is);size_t intsetBlobLen(intset *is);
一种数据结构。必定要提供类似插入。查询。删除这种接口。另外不要暴露内部使用的接口
,这里提供的接口,我们详细分析几个

初始化接口:

/* Create an empty intset. */intset *intsetNew(void) {    intset *is = malloc(sizeof(intset));    is->encoding = intrev32ifbe(INTSET_ENC_INT16);    is->length = 0;    return is; }
没什么难的,注意默认使用最低的2字节存储

/* Insert an integer in the intset */intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {    uint8_t valenc = _intsetValueEncoding(value);    uint32_t pos;    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. */        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. */        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);    }    _intsetSet(is,pos,value);    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);    return is;}

这个接口比較有难度。详细分析:

1、首先推断要添加的值的编码是否大于当前编码,大于则进行类型提升。并添加value

2、假设小于当前编码,首先查询数据是否存在,存在则返回,不存在则设置插入位置pos

3、又一次分配内存大小

4、移动数据。全部数据往后移动。复杂度有点高啊

5、插入数据,设置数据个数

当中。类型提升并插入value的接口例如以下:

/* Upgrades the intset to a larger encoding and inserts the given integer. */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;    /* First set new encoding and resize */    is->encoding = intrev32ifbe(newenc);    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. */    while(length--)        _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);    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);    return is;}
能够看到,类型提升的步骤例如以下:

1、由于整数集是有序的,所以首先推断要加入的数是正数还是负数,正数就在尾部加入,负数则在头部加入

2、添加内存大小

3、移动数据,这里和第一步挂钩。并且移动的过程比較难以理解,首先依据原来编码取出数据,然后依据新的编码插入数据

4、插入数据。在头部还是尾部插入

5、改动数据个数

另外移动数据的接口例如以下:

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);}
由于是连续的内存,找到移动的起始位置,然后memmove(),bingo!

!!

查找数据的接口实现:

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)) {            if (pos) *pos = intrev32ifbe(is->length);            return 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;        }    }    if (value == cur) {        if (pos) *pos = mid;        return 1;    } else {        if (pos) *pos = min;        return 0;    }}

还是个二分查找,niubility!!

!个人感觉这样的数据结构的高效就体如今这里。由于是有序。所以查找高速,由于是数组。所以插入。删除。是连续内存拷贝,也非常快

有时间突然想去看一下STL Vector的实现了,它的insert是怎样实现的?

本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5265268.html,如需转载请自行联系原作者

你可能感兴趣的文章
阿里云吴磊创办yunQ云桥,完成银杏谷资本2000万元天使轮融资 ...
查看>>
1月11日云栖精选夜读 | 阿里云获ITSS最高等级认证:公共云、专有云服务能力双一级 ...
查看>>
HPAIC人类蛋白质图谱分类挑战赛金牌经验分享
查看>>
【OSS全球加速】如何利用CNAME自定义加速区域
查看>>
foreman ubuntu16快速安装
查看>>
四步搞定阿里云RDS云数据库恢复到本地mysql数据库 ...
查看>>
PostgreSQL用户应掌握的高级SQL特性
查看>>
以优质氘代产品为刃,劈开生物原材料市场壁垒
查看>>
CodeHub#1 回顾 | 敏捷开发与动态更新在支付宝 App 内的实践
查看>>
看得见的百亿脱贫投入,看不见的阿里技术“脱贫代码”
查看>>
Java架构师必备技能:Redis+SpringMVC+RPC框架+MySql+JVM
查看>>
周博通 | 阿里开源首个 DL 框架、4000台服务器真实数据集;明年1月开源Blink
查看>>
Spring Boot系列实战文章合集(附源码)
查看>>
python设计模式(二十二):策略模式
查看>>
【专访】阿里云朱照远:边缘计算成为CDN下半场竞争新赛道
查看>>
接口调试与文档生成工具ApiPost的发送超时时间设置方法
查看>>
【Docker系列】全面认识Docker和基本指令
查看>>
详解蚂蚁金服 SOFAJRaft | 生产级高性能 Java 实现
查看>>
Vue-Router基础学习笔记
查看>>
填报表如何做到像 word 那样定时自动保存
查看>>