文章目录

  • 前言
  • 1. 哈希表
  • 2. 简单动态字符串
    • 使用SDS的好处
    • 对比C字符串
  • 3. 压缩列表
  • 4. 跳表
  • 5. 整数集合、双向链表
  • Redis数据类型与底层数据结构对照表

前言

一谈到Redis,马上能想到的就是:“快”,Redis之所以快,一方面是因为Redis的所有操作都在内存中完成,内存操作本身就很快,另一方面就要归功于它的数据结构了,高效的数据结构是Redis快的基石,当然,也不是所有的命令执行效率都很高,本文就来看看都有哪些时间复杂度较高的命名。

1. 哈希表

Redis除了本身的Hash、Set类型使用了哈希表之外,实际上Redis中所有的键值对也是用哈希表来保存的,哈希表大家应该已经非常熟悉了,虽然存在Hash冲突的问题,但可以通过rehash解决,所以寻找Key的效率很高,时间复杂度是O(1)。

并且Redis为了尽量避免一次rehash时间过长可能带来的阻塞问题,还采用了渐进式rehash的方式,简单点来说,就是每次只处理哈希表中一个索引位置上的元素,这样就把一次性大量的消耗,分摊变为了多次小量的消耗,避免了长耗时的操作。

2. 简单动态字符串

SDS(simple dynamic string),这是Redis中String类型的底层数据结构。

/* Note: sdshdr5 is never used, we just access the flags byte directly.* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {unsigned char flags; /* 3 lsb of type, and 5 msb of string length */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len; /* used */uint8_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {uint16_t len; /* used */uint16_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {uint32_t len; /* used */uint32_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {uint64_t len; /* used */uint64_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};

len:记录buf数组中已使用的字节的长度。
alloc:已分配的空间,不包含头和空终止符。
flags:前三位表示不同的sds结构体的类型,后五位预留。
buf:字节数组,用于保存字符串。

使用SDS的好处

1、获取字符串长度的复杂度降低
因为在sds的头中包含了len属性,所以在获取一个字符串长度时它的复杂度是O(1),而C字符串则必须遍历直到遇到’\0’,所以它的复杂度是O(n)。

2、避免内存溢出
在C字符串中为一个老的字符串追加字符,如果预先没有分配好足够的内存空间,则会导致数据溢出的问题,而使用sds则不会,因为sds提供的api会在空间不足的情况下,自动进行扩容操作。

sds sdscatlen(sds s, const void *t, size_t len) {size_t curlen = sdslen(s);//确保了空间足够足够拼接新的字符s = sdsMakeRoomFor(s,len);if (s == NULL) return NULL;memcpy(s+curlen, t, len);sdssetlen(s, curlen+len);s[curlen+len] = '\0';return s;
}

3、空间预分配

sds sdsMakeRoomFor(sds s, size_t addlen) {void *sh, *newsh;size_t avail = sdsavail(s);size_t len, newlen;char type, oldtype = s[-1] & SDS_TYPE_MASK;int hdrlen;/* Return ASAP if there is enough space left. *///如果空间足够,则直接返回if (avail >= addlen) return s;len = sdslen(s);sh = (char*)s-sdsHdrSize(oldtype);newlen = (len+addlen);//#define SDS_MAX_PREALLOC (1024*1024)//如果拼接后的字符串长度小于1M,则为新的字符串分配原长度2倍的空间if (newlen < SDS_MAX_PREALLOC)newlen *= 2;//如果超过1M,则多分配1M的空间elsenewlen += SDS_MAX_PREALLOC;type = sdsReqType(newlen);/* Don't use type 5: the user is appending to the string and type 5 is* not able to remember empty space, so sdsMakeRoomFor() must be called* at every appending operation. */if (type == SDS_TYPE_5) type = SDS_TYPE_8;hdrlen = sdsHdrSize(type);//如果类型没改变if (oldtype==type) {//重新分配已经分配好的内存newsh = s_realloc(sh, hdrlen+newlen+1);if (newsh == NULL) return NULL;s = (char*)newsh+hdrlen;} else {/* Since the header size changes, need to move the string forward,* and can't use realloc *///动态分配一个新的内存空间newsh = s_malloc(hdrlen+newlen+1);if (newsh == NULL) return NULL;memcpy((char*)newsh+hdrlen, s, len+1);s_free(sh);s = (char*)newsh+hdrlen;s[-1] = type;sdssetlen(s, len);}sdssetalloc(s, newlen);return s;
}

这就说明了,每次字符串的扩容,并不是扩充到刚好为当前字符串的长度,而是根据当前字符串的长度,要么扩充到原来的2倍,要么扩充1M。

这样做的目的是为了解决C字符串每次扩容都需要进行内存分配的问题,为了减少内存分配的次数,而预先分配一定的空间,使得原来扩充N次需要进行N次内存分配,优化到了扩充N次最多进行N次内存分配。

4、惰性删除
清空字符串操作,会将sds字符串长度修改为0,但是缓冲区并不会被回收,只是修改为可用空间,这样之后如果又要对字符串做拼接操作的话就不需要再重新分配内存空间。

void sdsclear(sds s) {sdssetlen(s, 0);s[0] = '\0';
}

对比C字符串

最后对比一下,相比C本身的字符串,SDS有哪些好处。

3. 压缩列表

压缩列表可以理解为是一种数组形式的数据结构,目的是为了提升内存的利用率,Redis数据类型中的List、Hash、Sorted Set底层实现时都会有用到压缩列表

压缩列表分为ziplist header、entries、end三个部分,其中头部由zlbytes、zltail、zllen组成,分别表示:列表长度、到达列表尾部的偏移量和列表中entry的数量,具体可见下图。

压缩列表这种数据结构,相对于Hash来说,减少了大量的指针存储,因此非常的节省内存使用,并且在检索第一个元素以及最后一个元素时可以利用zlbytes、zltail、zllen直接定位,效率也很高,不过,如果要检索其他元素,就没那么高效了,只能一个个遍历了,所以其时间复杂度是O(n)。

为了避免在元素较多的情况下由于时间复杂度较高导致查询效率变差的情况,Redis特地设置了两个配置项,分别如下:

hash-max-ziplist-entries:表示用压缩列表保存时哈希集合中的最大元素个数。
hash-max-ziplist-value:表示用压缩列表保存时哈希集合中单个元素的最大长度。

这两个只要有一个条件满足,Redis就会把压缩列表转为哈希表,从而避免查询效率变低的问题。

4. 跳表

跳表是Sorted Set数据类型的底层数据结构,跳表是在有序链表的基础上,通过增加多级索引,从而实现元素的快速定位,最关键的是,跳表的性能与红黑树差不多,但实现原理则比红黑树简单的多,并且查询、插入、删除的时间复杂度都是O(logN)。

摘自百度百科

5. 整数集合、双向链表

整数集合、双向链表都是比较简单的数据结构了,其中List类型除了使用到了压缩列表之外,还会使用双向链表来实现,整数集合则在Set类型时会使用到。

Redis数据类型与底层数据结构对照表

Redis中的数据类型 底层数据结构
String 简单动态字符串
Hash 哈希表、压缩列表
List 双向链表、压缩列表
Set 整数集合、哈希表
Sort Set 跳表、压缩列表

Redis底层数据结构介绍相关推荐

  1. 02 Redis 底层数据结构

    一.不同数据类型存储结构 Redis底层数据结构一共有 6 种,分别是简单动态字符串.双向链表.压缩列表.哈希表.跳表和整数数组.它们和数据类型的对应关系如下图所示: 1 数组与链表的区别 数组和链表 ...

  2. Redis底层数据结构详解(一)

    Redis底层数据结构 一.简单动态字符串SDS 1. SDS 2. 为什么Redis没用C语言原生字符串? 2.1 C语言中的字符串 2.2 使用SDS的好处 二.链表linkedlist 三.压缩 ...

  3. Redis底层数据结构详解

    Redis底层数据结构详解 我们知道Redis常用的数据结构有五种,String.List.Hash.Set.ZSet,其他的集中数据结构基本上也是用这五种实现的,那么,这五种是Redis提供给你的数 ...

  4. 保存到redis的字符串类型出现斜杆_深入浅出Redis:这次从Redis底层数据结构开始...

    1.概述 相信使用过Redis 的各位同学都很清楚,Redis 是一个基于键值对(key-value)的分布式存储系统,与Memcached类似,却优于Memcached的一个高性能的key-valu ...

  5. redis底层数据结构之跳跃表

    redis底层数据结构之跳跃表 redis 的zset有序连表为啥选择用跳跃表? 我们要思考一问题,首先多问问自己为什么,才容易理解它,ps:这是个人观点.首先我们选择的数据结构和算法原因有以下几种: ...

  6. Redis面试题-Redis底层数据结构

    本文参考 嗨客网 Redis面试题 Redis底层数据结构 Redis 的五大数据类型也称五大数据对象,即分别为 string. list. hash. set 和 zset,但 Redis 并没有直 ...

  7. Redis底层数据结构(图文详解)

    目录 前言 Redis为什么要使用2个对象?两个对象的好处 redisObject对象解析 String 类型 1.int 整数值实现 2.embstr 3.raw List 类型 1.压缩链表:zi ...

  8. redis底层数据结构(redis底层存储结构、源码分析)

    文章目录 前言 一.redis为什么快? 二.redis的底层数据结构 2.1.redis的底层存储的扩容机制 2.1.1.扩容时间 2.1.2.扩容多大 2.1.3.扩容后的rehash 2.1.4 ...

  9. redis底层数据结构简述

    2019独角兽企业重金招聘Python工程师标准>>> redis的数据库对象有五种,分别是字符串对象(key-value),列表对象(list),哈希对象(hash),集合对象(s ...

最新文章

  1. ns2的第一个tcl脚本
  2. System类+Math类+Arrays类
  3. 大公司体制内创新的困境
  4. 机器人砖机视频_全自动透水砖机生产线需严格遵守的标准工艺流程
  5. jQuery Mobile中列表listview(ol、ul)的data-*选项
  6. 买了一个 站立式办公 桌子。
  7. 《逐梦旅程——windows游戏编程之从零开始》笔记
  8. 汽车EMI/EMC测试标准ISO7637-2详解
  9. Java 第三方ui库_Uiautomator打包使用第三方库,报错的解决方案
  10. 第075封“情书”:百撕不得其解Tearing Cloth<Entagma>Houdini 2018
  11. Java学习总结篇一初识jvav
  12. 【拓展】腾讯十大最受欢迎的开源项目!
  13. 黑马程序员——结缘黑马
  14. 用文本编辑器编译cs文件
  15. 中国企业说专列国庆开行 北京地铁一号线披上中国红迎国庆
  16. ubuntu使用 ffmpeg 转换amr格式为mp3
  17. matlab ecu代码,嵌入式代码生成 - 汽车 ECU 产品级代码 - MATLAB Simulink
  18. 2020年中国数字减影血管造影系统(DSA)市场现状分析,DSA设备需求不断提升「图」
  19. DJI OSDK和DJI Onboard-SDK-ROS
  20. Linux 配置Git

热门文章

  1. 联邦学习分类及前景应用
  2. 安卓手机投屏软件_手机投屏软件哪个好?推荐这五款投屏神器
  3. ISP许可证办理攻略全了解
  4. 企业微信三方开发(三):网页授权登录
  5. 【linux内核-源码编译之centos7】
  6. 毕业论文用什么流程图软件比较好?
  7. cocos creator动态加载DragonBones
  8. 第1章 linux的历史演绎
  9. 数字游戏ABCD*E=DCBA-第11届蓝桥杯Scratch选拔赛真题精选
  10. Flowplayer一款免费的WEB视频播放器