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

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

一、数据结构

  1. typedef struct intset {
  2. uint32_t encoding;
  3. uint32_t length;
  4. int8_t contents[];
  5. } intset;

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

  1. #define INTSET_ENC_INT16 (sizeof(int16_t))
  2. #define INTSET_ENC_INT32 (sizeof(int32_t))
  3. #define INTSET_ENC_INT64 (sizeof(int64_t))

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

  1. static uint8_t _intsetValueEncoding(int64_t v) {
  2. if (v < INT32_MIN || v > INT32_MAX)
  3. return INTSET_ENC_INT64;
  4. else if (v < INT16_MIN || v > INT16_MAX)
  5. return INTSET_ENC_INT32;
  6. else
  7. return INTSET_ENC_INT16;
  8. }

intset是已排序好的整数集合,其大致结构如下:

  1. /*
  2. +--------+--------+--------...--------------+
  3. |encoding|length |contents(encoding*length)|
  4. +--------+--------+--------...--------------+
  5. */

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

  1. #if (BYTE_ORDER == LITTLE_ENDIAN)
  2. #define memrev16ifbe(p) ((void)(0))
  3. #define memrev32ifbe(p) ((void)(0))
  4. #define memrev64ifbe(p) ((void)(0))
  5. #define intrev16ifbe(v) (v)
  6. #define intrev32ifbe(v) (v)
  7. #define intrev64ifbe(v) (v)
  8. #else
  9. #define memrev16ifbe(p) memrev16(p)
  10. #define memrev32ifbe(p) memrev32(p)
  11. #define memrev64ifbe(p) memrev64(p)
  12. #define intrev16ifbe(v) intrev16(v)
  13. #define intrev32ifbe(v) intrev32(v)
  14. #define intrev64ifbe(v) intrev64(v)
  15. #endif

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

二、创建

  1. intset *intsetNew(void) {
  2. intset *is = zmalloc(sizeof(intset));
  3. is->encoding = intrev32ifbe(INTSET_ENC_INT16);
  4. is->length = ;
  5. return is;
  6. }

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

  1. /*此处用一根“-”表示一字节,后同
  2. +----+----+
  3. | 16| 0|
  4. +----+----+
  5. */

三、 操作

若有以下intset:

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

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

  1. /* Insert an integer in the intset */
  2. intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
  3. uint8_t valenc = _intsetValueEncoding(value);
  4. uint32_t pos;
  5. if (success) *success = ;
  6.  
  7. /* Upgrade encoding if necessary. If we need to upgrade, we know that
  8. * this value should be either appended (if > 0) or prepended (if < 0),
  9. * because it lies outside the range of existing values. */
  10. if (valenc > intrev32ifbe(is->encoding)) {
  11. /* This always succeeds, so we don't need to curry *success. */
  12. return intsetUpgradeAndAdd(is,value);
  13. } else {
  14. /* Abort if the value is already present in the set.
  15. * This call will populate "pos" with the right position to insert
  16. * the value when it cannot be found. */
  17. if (intsetSearch(is,value,&pos)) {
  18. if (success) *success = ;
  19. return is;
  20. }
  21.  
  22. is = intsetResize(is,intrev32ifbe(is->length)+);
  23. if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+);
  24. }
  25.  
  26. _intsetSet(is,pos,value);
  27. is->length = intrev32ifbe(intrev32ifbe(is->length)+);
  28. return is;
  29. }

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

  1. static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
  2. int min = , max = intrev32ifbe(is->length)-, mid = -;
  3. int64_t cur = -;
  4.  
  5. /* The value can never be found when the set is empty */
  6. if (intrev32ifbe(is->length) == ) {
  7. if (pos) *pos = ;
  8. return ;
  9. } else {
  10. /* Check for the case where we know we cannot find the value,
  11. * but do know the insert position. */
  12. if (value > _intsetGet(is,max)) {
  13. if (pos) *pos = intrev32ifbe(is->length);
  14. return ;
  15. } else if (value < _intsetGet(is,)) {
  16. if (pos) *pos = ;
  17. return ;
  18. }
  19. }
  20.  
  21. while(max >= min) {
  22. mid = ((unsigned int)min + (unsigned int)max) >> ;
  23. cur = _intsetGet(is,mid);
  24. if (value > cur) {
  25. min = mid+;
  26. } else if (value < cur) {
  27. max = mid-;
  28. } else {
  29. break;
  30. }
  31. }
  32.  
  33. if (value == cur) {
  34. if (pos) *pos = mid;
  35. return ;
  36. } else {
  37. if (pos) *pos = min;
  38. return ;
  39. }
  40. }

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

  1. /*
  2. find 6
  3. +----+----+--+--+--+--+--+--+--+
  4. | 16| 7| 1| 2| 3| 4| 5| 7| 8|
  5. +----+----+--+--+--+--+--+--+--+
  6. pos | 0| 1| 2| 3| 4| 5| 6|
  7. step1 |min=0
  8. |max=6
  9. |mid=(0+6)>>1=3
  10. |mid_val=4
  11.  
  12. pos | 0| 1| 2| 3| 4| 5| 6|
  13. step2 |min=4
  14. |max=6
  15. |mid=(4+6)>>1=5
  16. |mid_val=7
  17.  
  18. pos | 0| 1| 2| 3| 4| 5| 6|
  19. step3 |min=4
  20. |max=4
  21. |mid=(4+4)>>1=5
  22. |mid_val=5
  23.  
  24. pos | 0| 1| 2| 3| 4| 5| 6|
  25. step4 |min=5
  26. |max=4
  27. min>max break
  28. */

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

  1. static intset *intsetResize(intset *is, uint32_t len) {
  2. uint32_t size = len*intrev32ifbe(is->encoding);
  3. is = zrealloc(is,sizeof(intset)+size);
  4. return is;
  5. }

扩展后:

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

然后把原来在pos=5及之后的所有的元素向后移一格:

  1. static void intsetMoveTail(intset *is, uint32_t from, uint32_t to) {
  2. void *src, *dst;
  3. uint32_t bytes = intrev32ifbe(is->length)-from;
  4. uint32_t encoding = intrev32ifbe(is->encoding);
  5.  
  6. if (encoding == INTSET_ENC_INT64) {
  7. src = (int64_t*)is->contents+from;
  8. dst = (int64_t*)is->contents+to;
  9. bytes *= sizeof(int64_t);
  10. } else if (encoding == INTSET_ENC_INT32) {
  11. src = (int32_t*)is->contents+from;
  12. dst = (int32_t*)is->contents+to;
  13. bytes *= sizeof(int32_t);
  14. } else {
  15. src = (int16_t*)is->contents+from;
  16. dst = (int16_t*)is->contents+to;
  17. bytes *= sizeof(int16_t);
  18. }
  19. memmove(dst,src,bytes);
  20. }

移动后:

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

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

最后修改pos=5的值:

  1. static void _intsetSet(intset *is, int pos, int64_t value) {
  2. uint32_t encoding = intrev32ifbe(is->encoding);
  3.  
  4. if (encoding == INTSET_ENC_INT64) {
  5. ((int64_t*)is->contents)[pos] = value;
  6. memrev64ifbe(((int64_t*)is->contents)+pos);
  7. } else if (encoding == INTSET_ENC_INT32) {
  8. ((int32_t*)is->contents)[pos] = value;
  9. memrev32ifbe(((int32_t*)is->contents)+pos);
  10. } else {
  11. ((int16_t*)is->contents)[pos] = value;
  12. memrev16ifbe(((int16_t*)is->contents)+pos);
  13. }
  14. }

修改后并增加了length:

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

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

  1. static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
  2. uint8_t curenc = intrev32ifbe(is->encoding);
  3. uint8_t newenc = _intsetValueEncoding(value);
  4. int length = intrev32ifbe(is->length);
  5. int prepend = value < ? : ;
  6.  
  7. /* First set new encoding and resize */
  8. is->encoding = intrev32ifbe(newenc);
  9. is = intsetResize(is,intrev32ifbe(is->length)+);
  10.  
  11. /* Upgrade back-to-front so we don't overwrite values.
  12. * Note that the "prepend" variable is used to make sure we have an empty
  13. * space at either the beginning or the end of the intset. */
  14. while(length--)
  15. _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
  16.  
  17. /* Set the value at the beginning or the end. */
  18. if (prepend)
  19. _intsetSet(is,,value);
  20. else
  21. _intsetSet(is,intrev32ifbe(is->length),value);
  22. is->length = intrev32ifbe(intrev32ifbe(is->length)+);
  23. return is;
  24. }

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

删除操作:

  1. intset *intsetRemove(intset *is, int64_t value, int *success) {
  2. uint8_t valenc = _intsetValueEncoding(value);
  3. uint32_t pos;
  4. if (success) *success = ;
  5.  
  6. if (valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,&pos)) {
  7. uint32_t len = intrev32ifbe(is->length);
  8.  
  9. /* We know we can delete */
  10. if (success) *success = ;
  11.  
  12. /* Overwrite value with tail and update length */
  13. if (pos < (len-)) intsetMoveTail(is,pos+,pos);
  14. is = intsetResize(is,len-);
  15. is->length = intrev32ifbe(len-);
  16. }
  17. return is;
  18. }

找到指定元素之后,直接把后面的内存移至前面,然后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. 频繁插入(insert)的业务,用什么存储引擎更合适? | 数据库系列(转)

    本文来自微信公众号 继续回答星球水友提问: 沈老师,MyISAM只支持表锁,但网上文章却说,在并发插入量比较大的时候,比较适合使用MyISAM,这矛盾吗? 这个问题,涉及MySQL表锁的一些细节,借着 ...

  2. influxdb基础那些事儿

    InfluxDB是一个开源的时序数据库,使用GO语言开发,特别适合用于处理和分析资源监控数据这种时序相关数据.而InfluxDB自带的各种特殊函数如求标准差,随机取样数据,统计数据变化比等,使数据统计 ...

  3. python从excel中读取数据传给其他函数使用

    首先安装xlrd库 pip install xlrd 方法1: 表格内容如下: 场景描述,读取该表格A列数据,然后打印出数据 代码何解析如下: import xlrd #引入xlrd库 def exc ...

  4. Python中将变量按行写入txt文本中

    案例一: 讲数组a 循环写入名称为2.txt的文档中 # -*-coding:utf8-*- import requests from lxml import etree a=[1,2,3,4,5,6 ...

  5. CSS-04-层叠选择器

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  6. nginx之文件配置

    nginx配置规则 nginx由受配置文件中指定的指令控制的模块组成 伪指令分为简单伪指令和块伪指令 简单的指令由名称和参数组成,这些名称和参数之间用空格分隔,并以分号(;)结尾 块指令的结构 与 简 ...

  7. laravel 工厂模式到容器

    下面实现了查人拥有超能力的三种方式 第一种最基本的类引用实现 1 <?php /** * 目的:代码的完善来说明从 基础类的调用到 工厂类的使用 再到容器的出现的原因 * (首先要明白工厂类和容 ...

  8. Spring5.x源码分析 | 从踩坑到放弃之环境搭建

    Spring5.x源码分析--从踩坑到放弃之环境搭建 前言 自从Spring发行4.x后,很久没去好好看过Spring源码了,加上最近半年工作都是偏管理和参与设计为主,在技术细节上或多或少有点疏忽,最 ...

  9. vs2017项目上传到github

    如果要把项目提交到一个厂库里面,需要建个git存储库,比如选择新建git库选择VSVIEW文件夹,以后在这个文件夹下的项目,提交时都会提交到VSVIEW这个github仓库 选择的文件夹不在git文件 ...

  10. sql中常量和变量的引用

    String name =jtf.getText().trim(); String sql="select * from stu where stuname='  "+name+& ...