redis中整数集合intset相关的文件为:intset.h与intset.c

intset的所有操作与操作一个排序整形数组 int a[N]类似,只是根据类型做了内存上的优化。

一、数据结构

 typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;

intset的数据结构比较简单,使用了一个变长结构体,成员length记录当前成员数量,成员encoding记录当前的int类型,共有以下三种:

 #define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

并使用以下方法进行判断类型:

 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是已排序好的整数集合,其大致结构如下:

 /*
+--------+--------+--------...--------------+
|encoding|length |contents(encoding*length)|
+--------+--------+--------...--------------+
*/

intset严格按照小端字节序进行存储,不论机器的字节序类型。如果是大端机器,需要进行转换,才进行存储。endianconv.h中有如下定义:

 #if (BYTE_ORDER == LITTLE_ENDIAN)
#define memrev16ifbe(p) ((void)(0))
#define memrev32ifbe(p) ((void)(0))
#define memrev64ifbe(p) ((void)(0))
#define intrev16ifbe(v) (v)
#define intrev32ifbe(v) (v)
#define intrev64ifbe(v) (v)
#else
#define memrev16ifbe(p) memrev16(p)
#define memrev32ifbe(p) memrev32(p)
#define memrev64ifbe(p) memrev64(p)
#define intrev16ifbe(v) intrev16(v)
#define intrev32ifbe(v) intrev32(v)
#define intrev64ifbe(v) intrev64(v)
#endif

具体实现在endianconv.c中,此处略过。

二、创建

 intset *intsetNew(void) {
intset *is = zmalloc(sizeof(intset));
is->encoding = intrev32ifbe(INTSET_ENC_INT16);
is->length = ;
return is;
}

刚创建好的intset是空的,默认使用最小的类型。其结构为:

 /*此处用一根“-”表示一字节,后同
+----+----+
| 16| 0|
+----+----+
*/

三、 操作

若有以下intset:

 /*
+----+----+--+--+--+--+--+--+--+
| 16| 7| 1| 2| 3| 4| 5| 7| 8|
+----+----+--+--+--+--+--+--+--+
|contents */

现在插入一个数字6,需要调用以下方法:

 /* 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 = ; /* 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 = ;
return is;
} is = intsetResize(is,intrev32ifbe(is->length)+);
if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+);
} _intsetSet(is,pos,value);
is->length = intrev32ifbe(intrev32ifbe(is->length)+);
return is;
}

因int16_t足以存储数字“6”,所以新插入数字的int类型与intset一致,然后需要查找插入的pos:

 static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
int min = , max = intrev32ifbe(is->length)-, mid = -;
int64_t cur = -; /* The value can never be found when the set is empty */
if (intrev32ifbe(is->length) == ) {
if (pos) *pos = ;
return ;
} else {
/* Check for the case where we know we cannot find the value,
* but do know the insert position. */
if (value > _intsetGet(is,max)) {
if (pos) *pos = intrev32ifbe(is->length);
return ;
} else if (value < _intsetGet(is,)) {
if (pos) *pos = ;
return ;
}
} while(max >= min) {
mid = ((unsigned int)min + (unsigned int)max) >> ;
cur = _intsetGet(is,mid);
if (value > cur) {
min = mid+;
} else if (value < cur) {
max = mid-;
} else {
break;
}
} if (value == cur) {
if (pos) *pos = mid;
return ;
} else {
if (pos) *pos = min;
return ;
}
}

因intset是已排序好的,所以使用了二分查找。过程如下

 /*
find 6
+----+----+--+--+--+--+--+--+--+
| 16| 7| 1| 2| 3| 4| 5| 7| 8|
+----+----+--+--+--+--+--+--+--+
pos | 0| 1| 2| 3| 4| 5| 6|
step1 |min=0
|max=6
|mid=(0+6)>>1=3
|mid_val=4 pos | 0| 1| 2| 3| 4| 5| 6|
step2 |min=4
|max=6
|mid=(4+6)>>1=5
|mid_val=7 pos | 0| 1| 2| 3| 4| 5| 6|
step3 |min=4
|max=4
|mid=(4+4)>>1=5
|mid_val=5 pos | 0| 1| 2| 3| 4| 5| 6|
step4 |min=5
|max=4
min>max break
*/

6在intset中不存在,查找到需要插入到pos=5的位置,此时首先要扩展intset的content:

 static intset *intsetResize(intset *is, uint32_t len) {
uint32_t size = len*intrev32ifbe(is->encoding);
is = zrealloc(is,sizeof(intset)+size);
return is;
}

扩展后:

 /*
+----+----+--+--+--+--+--+--+--+--+
| 16| 7| 1| 2| 3| 4| 5| 7| 8| |
+----+----+--+--+--+--+--+--+--+--+
pos | 0| 1| 2| 3| 4| 5| 6| 7|
*/

然后把原来在pos=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);
}

移动后:

 /*
+----+----+--+--+--+--+--+--+--+--+
| 16| 7| 1| 2| 3| 4| 5| 7| 7| 8|
+----+----+--+--+--+--+--+--+--+--+
pos | 0| 1| 2| 3| 4| 5| 6| 7|
*/

其使用memmove,并不全修改未覆盖到的内存,所以此时pos=5的值 还是7

最后修改pos=5的值:

 static void _intsetSet(intset *is, int pos, int64_t value) {
uint32_t encoding = intrev32ifbe(is->encoding); if (encoding == INTSET_ENC_INT64) {
((int64_t*)is->contents)[pos] = value;
memrev64ifbe(((int64_t*)is->contents)+pos);
} else if (encoding == INTSET_ENC_INT32) {
((int32_t*)is->contents)[pos] = value;
memrev32ifbe(((int32_t*)is->contents)+pos);
} else {
((int16_t*)is->contents)[pos] = value;
memrev16ifbe(((int16_t*)is->contents)+pos);
}
}

修改后并增加了length:

 /*
+----+----+--+--+--+--+--+--+--+--+
| 16| 8| 1| 2| 3| 4| 5| 6| 7| 8|
+----+----+--+--+--+--+--+--+--+--+
pos | 0| 1| 2| 3| 4| 5| 6| 7|
*/

如果此时要插入的数字是65536,超出了int16_t所能表示的范围,要先进行扩展int类型操作:

 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 < ? : ; /* First set new encoding and resize */
is->encoding = intrev32ifbe(newenc);
is = intsetResize(is,intrev32ifbe(is->length)+); /* 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,,value);
else
_intsetSet(is,intrev32ifbe(is->length),value);
is->length = intrev32ifbe(intrev32ifbe(is->length)+);
return is;
}

因其超出原来的int类型所能表示的范围,若为正数,一定是最大的,则应该插入在intset最后,否则应该在最前面。扩展完之后,从后往前将原来的数字,以新的int类型,放置在新的位置上,保证不会有未处理的数字被覆盖,处理完整。

删除操作:

 intset *intsetRemove(intset *is, int64_t value, int *success) {
uint8_t valenc = _intsetValueEncoding(value);
uint32_t pos;
if (success) *success = ; if (valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,&pos)) {
uint32_t len = intrev32ifbe(is->length); /* We know we can delete */
if (success) *success = ; /* Overwrite value with tail and update length */
if (pos < (len-)) intsetMoveTail(is,pos+,pos);
is = intsetResize(is,len-);
is->length = intrev32ifbe(len-);
}
return is;
}

找到指定元素之后,直接把后面的内存移至前面,然后resize。

redis 5.0.7 下载链接

http://download.redis.io/releases/redis-5.0.7.tar.gz

源码阅读顺序参考:

https://github.com/huangz1990/blog/blob/master/diary/2014/how-to-read-redis-source-code.rst

redis 5.0.7 源码阅读——整数集合intset的更多相关文章

  1. redis 5.0.7 源码阅读——跳跃表skiplist

    redis中并没有专门给跳跃表两个文件.在5.0.7的版本中,结构体的声明与定义.接口的声明在server.h中,接口的定义在t_zset.c中,所有开头为zsl的函数. 一.数据结构 单个节点: t ...

  2. redis 5.0.7 源码阅读——字典dict

    redis中字典相关的文件为:dict.h与dict.c 与其说是一个字典,道不如说是一个哈希表. 一.数据结构 dictEntry typedef struct dictEntry { void * ...

  3. redis 5.0.7 源码阅读——双向链表

    redis中双向链表相关的文件为:adlist.h与adlist.c 一.数据结构 redis里定义的双向链表,与普通双向链表大致相同 单个节点: typedef struct listNode { ...

  4. redis 5.0.7 源码阅读——动态字符串sds

    redis中动态字符串sds相关的文件为:sds.h与sds.c 一.数据结构 redis中定义了自己的数据类型"sds",用于描述 char*,与一些数据结构 typedef c ...

  5. redis 5.0.7 源码阅读——压缩列表ziplist

    redis中压缩列表ziplist相关的文件为:ziplist.h与ziplist.c 压缩列表是redis专门开发出来为了节约内存的内存编码数据结构.源码中关于压缩列表介绍的注释也写得比较详细. 一 ...

  6. Linux 0.11源码阅读笔记-文件管理

    Linux 0.11源码阅读笔记-文件管理 文件系统 生磁盘 未安装文件系统的磁盘称之为生磁盘,生磁盘也可以作为文件读写,linux中一切皆文件. 磁盘分区 生磁盘可以被分区,分区中可以安装文件系统, ...

  7. Linux 0.11源码阅读笔记-中断过程

    Linux 0.11源码阅读笔记-中断过程 是什么中断 中断发生时,计算机会停止当前运行的程序,转而执行中断处理程序,然后再返回原被中断的程序继续运行.中断包括硬件中断和软件中断,硬中断是由外设自动产 ...

  8. Linux 0.11源码阅读笔记-总览

    Linux 0.11源码阅读笔记-总览 阅读源码的目的 加深对Linux操作系统的了解,了解Linux操作系统基本架构,熟悉进程管理.内存管理等主要模块知识. 通过阅读教复杂的代码,锻炼自己复杂项目代 ...

  9. redis 4.0.8 源码包安装集群

    系统:centos 6.9软件版本:redis-4.0.8,rubygems-2.7.7,gcc version 4.4.7 20120313,openssl-1.1.0h,zlib-1.2.11 y ...

随机推荐

  1. 从零开始ming的多人联机游戏--游戏客户端(1)六边形地图

    打算做的小游戏是一个多人联机的策略类游戏,类似于<文明>那种 游戏的玩法并不确定,开这个坑主要是为了入门后端开发,顺便熟悉下游戏开发 这篇文章使用unity,实现了六边形单元地图的创建.后 ...

  2. Mondriaan's Dream 轮廓线DP 状压

    Mondriaan's Dream 题目链接 Problem Description Squares and rectangles fascinated the famous Dutch painte ...

  3. 讲解 json 和 pickle 模块

    首先是引入json 和 pickle 的原因是 普通的方法支持的数据类型太少 局限性大  比如下面的例子 dit = {'name':'deng1mei','age':'26','sex':'girl ...

  4. 在Centos7上安装Oracle

    环境: 硬盘30G:2G RAM:Centos7:Oracle 11G: 1.创建组和用户 [zzd@localhost ~]$ su root #切换到root Password: [root@lo ...

  5. 按照相应的格式获取系统时间并将其转化为SQL中匹配的(date)时间格式

    在获取时间时需要对时间格式进行设置,此时就需要用到SimpleDateFormat 类 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM ...

  6. 使用npm发布、查看、引用、删除自己的包[小白推荐]

    前言  怎么使用npm发布自己的组件包呢?博主看了不少相关的文章,都没有一个好的讲述.要么丢三落四,要么就是复杂到外行人根本看不懂,甚至无从下手.于是,在博主的潜心钻研下,终于成功了!所以,我打算写一 ...

  7. HDU_2571_DP

    http://acm.hdu.edu.cn/showproblem.php?pid=2571 简单dp,从上到下,从左到右依次更新每一格的最大幸运值. #include<iostream> ...

  8. Java程序员都需要懂的「反射」

    前言 只有光头才能变强. 文本已收录至我的GitHub精选文章,欢迎Star:https://github.com/ZhongFuCheng3y/3y 今天来简单写一下Java的反射.本来没打算写反射 ...

  9. 1 使用MySQL

    1.1 连接 主机名(localhost) 端口(3306) 一个合法的用户名 用户口令 1.2 选择数据库 USE crashcourse 1.3 了解数据库和表 SHOW databases; s ...

  10. 突破CRUD | 万能树工具类封装

    0.学完本文你或许可以收获 感受一个树工具从初始逐步优化完善的过程 树工具封装的设计思考与实现思路 最后收获一款拿来即用的树工具源代码 对于前端树组件有一定了解和使用过的同学可直接跳跃到第3章节开始. ...