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

Redis之intset数据结构的更多相关文章

  1. Redis 5种数据结构使用及注意事项

    1优缺点 非常非常的快,有测评说比Memcached还快(当大家都是单CPU的时候),而且是无短板的快,读写都一般的快,所有API都差不多快,也没有MySQL Cluster.MongoDB那样更新同 ...

  2. Redis(二)--- Redis的底层数据结构

    1.Redis的数据结构 Redis 的底层数据结构包含简单的动态字符串(SDS).链表.字典.压缩列表.整数集合等等:五大数据类型(数据对象)都是由一种或几种数结构构成. 在命令行中可以使用 OBJ ...

  3. 【Redis】内部数据结构自顶向下梳理

    本博客将顺着自顶向下的思路梳理一下Redis的数据结构体系,从数据库到对象体系,再到底层数据结构.我将基于我的一个项目的代码来进行介绍:daredis.该项目中,使用Java实现了Redis中所有的数 ...

  4. redis 五种数据结构详解(string,list,set,zset,hash)

    redis 五种数据结构详解(string,list,set,zset,hash) Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存 ...

  5. (2)redis的基本数据结构是动态数组

    redis的基本数据结构是动态数组 一.c语言动态数组 先看下一般的动态数组结构 struct MyData { int nLen; ]; }; 这是个广泛使用的常见技巧,常用来构成缓冲区.比起指针, ...

  6. 2.Redis五种数据结构

    2.Redis五种数据结构2.1 预备2.1.1 全局命令2.1.2 数据结构和内部编码2.1.3 单线程架构2.2 字符串2.2.1 命令2.2.2 内部编码2.2.3 典型使用场景2.3 哈希2. ...

  7. Redis指令与数据结构(二)

    0.Redis目录结构 1)Redis介绍及部署在CentOS7上(一) 2)Redis指令与数据结构(二) 3)Redis客户端连接以及持久化数据(三) 4)Redis高可用之主从复制实践(四) 5 ...

  8. redis 五种数据结构详解(string,list,set,zset,hash),各种问题综合

    redis 五种数据结构详解(string,list,set,zset,hash) https://www.cnblogs.com/sdgf/p/6244937.html redis 与 spring ...

  9. 【Redis】redis 五种数据结构详解(string,list,set,zset,hash)

    redis 五种数据结构详解(string,list,set,zset,hash) Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存 ...

随机推荐

  1. UVA 839 Not so Mobile (递归建立二叉树)

    题目连接:http://acm.hust.edu.cn/vjudge/problem/19486 给你一个杠杆两端的物体的质量和力臂,如果质量为零,则下面是一个杠杆,判断是否所有杠杆平衡. 分析:递归 ...

  2. Linux命令之kill

    kill [-s signal | -p] [ --] pid… kill –l [signal] 终止指定进程.命令kill将指定的信号发送到指定的进程或进程组.如果没有指定信号,则发送SIGTER ...

  3. pthread条件变量

    pthread条件变量等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返 ...

  4. 【贪心】【线性基】bzoj2460 [BeiJing2011]元素 / bzoj3105 [cqoi2013]新Nim游戏

    p2460: #include<cstdio> #include<algorithm> using namespace std; #define N 1001 typedef ...

  5. [POI2018]Prawnicy

    题目大意: 有$n(n\le10^6)$个线段,每个线段覆盖的范围是$[l_i,r_i]$,要求从中选取$k(k\le10^6)$个线段使得这些线段覆盖范围的交集最大,求最大交集及任意一种方案. 思路 ...

  6. 使用ASP.Net WebAPI构建REST服务(七)——调试工具

    由于WebAPI本身是基于HTTP协议的,在开发过程中,我们可以使用浏览器或Fiddler等HTTP工具辅助开发.与此同时,微软也提供了一些工具方便我们调试,使得开发更加简单快捷,本文就简单的介绍一下 ...

  7. faststone 注册码

    用户名:c1ikm密码:AXMQX-RMMMJ-DBHHF-WIHTV 或 AXOQS-RRMGS-ODAQO-APHUU

  8. python3+django使用celery执行某些任务失败的解决方案

    .在celery 的worker启动窗口设置export PYTHONOPTIMIZE=1 export PYTHONOPTIMIZE=1 /usr/local/python36/bin/celery ...

  9. 用iptables做NAT代理,使内网机器上外网

    现状:服务器A只有一个内网IP,不能上外网,内网IP与服务器B内网相通:服务器B有一个内网IP和公网IP.想实现服务器A也能上外网. 1 2 3 4 服务器A:内网网卡:eth0 内网IP:192.1 ...

  10. Linux程序编译链接动态库版本号的问题

    不同版本号的动态库可能会不兼容,假设程序在编译时指定动态库是某个低版本号.执行是用的一个高版本号,可能会导致无法执行. Linux上对动态库的命名採用libxxx.so.a.b.c的格式.当中a代表大 ...