Redis的数据结构与对象

  • redis数据库里的键值对都是由object组成的,数据库建总是一个字符串对象。
  • redis数据库里的值可以是字符串对象,列表对象,哈希对象,集合对象,有序集合对象中的一种。

Redis的SDS(simple dynamic string ,简单动态字符串)

比如我们可以在redis客户端敲

redis> set msg "helloRedis"

那么就会在Redis数据库中创建一个新的键值对,keg是msg,value是helloRedis,两个都是SDS。

又比如在redis客户端敲

redis>rpush number 1 2 3

这也就是一个list对象(列表对象),numbe为键,1,2,3为值。经过这几个小的例子你会发现Redis的用法很简单,SDS还可以被用来作缓冲区(buffer):AOF模块的AOF缓冲区和客户端状态中的输入缓冲区。

说了这么多的SDS,那么到底什么是SDS,SDS又是怎么实现的呢,那它又和我们传统的字符串有什么区别呢?

我们可以用c的结构体来表示SDS的定义信息

struct sds{
int len;
int free;
char buf[];
}

那这些都是代表什么意思呢?
len:用来记录字符串的长度,也就是buf已使用的字节数量
free:用来记录buf中剩余的数量
buf:是一个char数组,用来保存字符串的每个字符,字符串最后必须是一个空字符’\0’,不占用空间但是占用位置。这些添加字符串后缀都是由redis的内部函数来实现。

不止这一点redis的设计和c很像,redis还有很多和c相同的地方,因为这样就可以直接重用一些c写好的函数。

现在是不是对SDS有一些了解了,从结构可以看出它比c中的字符串多了len和free,这写参数可以决定怎样的性能呢?

redis获取字符串长度时间复杂度低,保证了效率

  • SDS有len来记录字节数,获取字符串长度操作时间复杂的理所当然的为O(1)
  • 普通字符串它要遍历计数,他要找到最后一个字符,因此它的时间复杂度为O(n),因此SDS的strlen函数不会对性能造成任何影响。

SDS不会造成缓冲去溢出

这个原因的实质还是因为没有len,我们假设在计算机内存中按顺序排列着S1,S2字符串,S2紧跟着S1字符串。如果是普通字符串进行拼接操作

  • Reids如果使用拼接函数(sdscut),计算S1拼接后的字符串长度很简单并调用函数为S1分配足够的空间,然后进行字符串拼接操作就不会造成由于S1内存不够而溢出到S2上去。
  • 普通字符串要使用拼接函数(strcut)还必须要计算所占空间数,并且一不小心忘记这步操作就可能会使S1拼接后的字符串由于没有位置而挤到S2的空间去,造成S1的缓存溢出。

SDS会很大程度减少内存重分配次数

为什么这么说呢?相信大家以前也做过字符串修改的函数,我们当时函数底层是怎样的呢?在我们每次修改字符串时系统都会执行内存重分配算法,如果不经常修改的话这都不是问题(前提是不粗心,不然增的时候可能会造成内存溢出,删的时候可能会造成内存泄漏),但是数据库就不一样,因为数据库的修改操作很频繁,因此redis采用的缓存策略。

  • redis的预分配策略(主要优化字符个数增加问题):如果字符串小于1mb,那就给它再分配同样大小的空间作为预分配空间。比如本来是10个字节(len的值)在reids的策略后就是20个字节+一个空字符=21。如果大于1mb的话,那么就会给它分配1mb的内存,比如本来是10mb+1byte,在分配策略后就20mb+1byte,这样在频繁的修改过程中不会经常去调用内存分配,因此在性能上就占优一点。
  • 惰性空间释放(主要优化字符个数减少问题):如果是缩短了字符串,那redis并不会立即把它多余的空间释放掉,而是将它加入到free中可以再一度的优化字符串增长操作。但是如果在空间紧张或者有需要的时候redis也有相应的API来释放未使用的空间,因此也就不用担心内存浪费的问题了。

二进制安全

这个也是c中的字符串中比较难受的事情了,比如我们输入一串数字再加上空格再加上一串数字,c就只会把它的前一段写进去,因为c判断结束的标识是空字符。但是Redis就不会出现这种问题,输入进去什么样子,输出出来就是什么样子,为什么呢?归根结底还是因为有len,因为它可以记住个数而不是一味的根据空字符来判断是不是结束了,因此它上边就可以存任何类型的东西。比如传一串二进制字符串,c可能得需要很复杂的处理,但是Redis根据SDS的定义就可以很轻松的解决这个问题。那Redis可以不给末尾加\0也可以实现为什么要加,还是因为它可以重用一些c的库函数。

总结:

SDS相对于普通字符串的优点(归根结都还是结构体的设计问题)

  • 获取长度复杂度小
  • 杜绝缓冲的溢出
  • 减少再修改字符串时内存重分配的次数
  • 二进制安全

Redis中的链表

因为c里没有链表库,因此Redis就自己实现了链表,那链表在Redis中扮演一个什么的角色呢?它在Redis中的应用非常广泛,比如链表键,发布与订阅,慢查询,监视器等功能,Redis服务器本身还使用链表来保存客户端的状态信息。
链表的操作就很简单了,因为它就和糖葫芦一样一个一个穿成一串。没有写过或者了解链表的可以去一些数据结构或者算法导论等一些书上去参考更详细的,我这就讲个中心意思。

设计数据结构模型,给Node模型加上前驱和后记和节点的值。

typedef struct listNode{struct lsitNode *preb;struct listNode *next;void *value;}listNode;

然后就通过指针操作头插或尾插进行连接成一个双向链表。

但是Redis是以高性能著称的,按照上述设计那不就是一个普通的单链表么,因此个数据结构仍有改进的空间。Redis后来就自己设计了如下结构的节点结构。

typedef struct list{listNode *head;//表头节点listNode *tail;//表尾unsigned long len;//链表所包含的字节数void *( * dup)(void *ptr);//节点复制函数void (*free)(void *ptr);//节点释放函数int (*match)(void *ptr,void key);//节点值对比函数,对比链表节点所保存的值和另一个输入值是否相等}list;

这种相对于上一种的优势所在是带有长度计数器,和节点的特定函数。
可以总的来说一下Redis的链表实现的特性:
1.双向链表获取前驱和后继的时间复杂度都是O(1)
2.是一个没有头节点的链表,最小是空
3.带有表头和表尾,获取头和尾时间复杂度为O(1)
4.多种功能:几个特殊设定的函数,可用于保存各种不同类型的值

虽然感觉上述这些并不是很难懂,但是它都设计的很巧妙,对查询和修改都优化了不少。

Redis设计与实现之SDS和链表相关推荐

  1. 《Redis设计与实现》笔记|SDS动态字符串|链表字典跳跃表整数集合压缩列表结构|redis中的对象|数据库原理|RDB持久化|AOF持久化|事件与多路利用模型|发布订阅原理|事务原理|慢查询日志

    <Redis设计与实现>笔记 前记: 参考配套网站:http://redisbook.com 带注释的源码地址:https://github.com/huangz1990/redis-3. ...

  2. Redis 数据结构 :SDS、链表、字典、跳表、整数集合、压缩列表

    文章目录 SDS 结构分析 内存策略 空间预分配 惰性空间释放 总结 链表 结构分析 总结 字典 结构分析 rehash 渐进式rehash 总结 跳表 结构分析 总结 整数集合 结构分析 升级 降级 ...

  3. 《redis 设计与实现》读书笔记

    大家好,我是烤鸭:     <redis 设计与实现>,读书笔记. 第一部分 数据结构与对象 第2章 简单动态字符串 Redis 使用SDS 作为字符串表示. O(1) 复杂度获取字符串长 ...

  4. Redis设计与实现阅读总结(一)数据结构和对象

    大家有什么建议欢迎在下方评论提出,多多讨论 大家可能发现这个部分写的很粗略,因为redis本身设计的就是很简单的,这些数据结构基本没有太多好写的,主要是写 了后做一个总结 1. SDS(简单动态字符串 ...

  5. Redis 设计与实现 读书笔记(菜鸟版)

    Redis 设计与实现 读书笔记(简略版) 写在前面 第一章(内部数据结构) SDS List Dictionary Rehash Rehash 与 COW 渐进式Rehash 字典收缩 Skipli ...

  6. Redis设计与实现详解二:Redis数据库实现

    Redis设计与实现详解一:数据结构与对象 Redis设计与实现详解三:多机功能实现 Redis设计与实现详解四:其他单机功能 数据库 服务器中的数据库 Redis服务器将所有数据库都保存在服务器状态 ...

  7. Redis设计与实现——对象

    文章目录 对象的类型与编码 字符串对象 编码转换 列表对象 编码转换 哈希对象 编码转换 集合对象 编码转换 有序集合对象 为什么zset同时使用跳跃表和字典来实现? 编码转换 Redis 的底层数据 ...

  8. 《Redis设计与实现》学习笔记

    Redis 本文会有一些Redis和Java容器对象的对比,一个是分布式数据库,一个是JVM内部数据容器,应用场景不同,仅仅是为了加深对Redis"数据库"的认识,加深对Redis ...

  9. Redis | 第6章 事件与客户端《Redis设计与实现》

    第6章 事件与客户端 前言 1. 事件 1.1 文件事件 1.1.1 I/O 多路复用程序的实现 1.1.2 事件类型与 API 1.1.3 文件事件的处理器 1.2 时间事件 1.2.1 API 1 ...

最新文章

  1. ISME:高手开杠-‘1%的微生物可培养’到底为哪般?
  2. python参数之间用什么分割_如何根据python的两个参数在美丽的汤中分割
  3. 学习 FPGA之前的基础知识
  4. 求两个数组的最长重复子数组 Maximum Length of Repeated Subarray
  5. 比特币钱包(2) BIP32 HD钱包之生成子密钥
  6. 内存溢出_关于PermGen Space内存溢出解决方案
  7. 如果Java快死了,那么它肯定看起来非常健康
  8. linux系统文件的复制,linux操作系统文件复制操作
  9. MYSQL-实现ORACLE- row_number() over(partition by ) 分组排序功能
  10. 相机标定的来龙去脉(详解标定原理、畸变矫正原理、使用经验)
  11. ArcCatalog中连接SDE数据库
  12. 逻辑思维与写作-第一章,第二章-随笔笔记
  13. 360全景倒车影像怎么看_360度全景倒车影像真的很有用?水分有多少!
  14. Linux 异常:The following signatures couldn‘t be verified because the public key is not available
  15. matlab正序零序负序,正序负序零序的理解
  16. Vlog 是短视频发展的新催化剂?
  17. 小米游戏本退出安全模式/win10安全模式密码
  18. 【转载】设备唯一标识方法(Unique Identifier):如何在Windows系统上获取设备的唯一标识
  19. 区块链取证设备,了解一下
  20. 闲的发慌系列01-家庭版NAS

热门文章

  1. 分分钟手写http server
  2. 常用的sql server 函数、存储过程、临时表总结
  3. flex 鼠标放在组件上变手型
  4. PHP高级编程之消息队列
  5. 操作系统实验报告8:进程间通信—消息机制
  6. oracle参数文件initorcl位置,oracle 参数文件详解
  7. iOS之深入解析单例的实现和销毁的底层原理
  8. 152. 乘积最大子数组
  9. 10.1.3 标签分类与嵌套
  10. 2018年第九届蓝桥杯C/C++ C组国赛 —— 第二题:最大乘积