before

  • C语言基础
  • Redis基础

导入

redis的命令如下:

            set x "hello";
get x;
hello

  Redis作为一种存储字符串的缓存结构,其具体实现是由C语言完成,在C语言中,字符串是通过字符数组实现的,即char[],那么Redis对于字符串的实现是不是也是基于字符数组吗?不是的,Redis对字符串的处理是通过SDS(Simple Dynamic String)实现的。

SDS介绍

  SDS(Simple Dynamic String)简单动态字符串,它是由C语言完成,如下是其具体实现

Redis作为一种存储字符串的缓存结构,其具体实现是由C语言完成,在C语言中,字符串是通过字符数组实现的,即char[],那么Redis对于字符串的实现是不是也是基于字符数组吗?不是的,Redis对字符串的处理是通过SDS(Simple Dynamic String)实现的。

struct sdshdr{
//记录buf数组已使用字节的数量
//等于SDS所保存字符串的长度
int length;
//记录buf数组未使用字节的数量
int free;
//buf数组
char[] buf;
}; 看看redis的示例:
sdshdr
free 0
length 5
buf -->|'R'|'e'|'d'|'i'|'s'|'\0'|
解释:
- free为0,表示这个SDS没有分配任何未使用的空间
- length为5,表示这个SDS保存了一个长度为5的字符串
- buf数组中保存着“Redis”字符串

  SDS遵循C字符串以空字符串结尾的惯例,保存空字符串的1字节空间不计算在SDS的len属性之中。

  再看看SDS的free不为0的情况:

   sdshdr
free 3
length 5
buf -->|'R'|'e'|'d'|'i'|'s'| | | |

  free的值为3,表示这个SDS分配了三个空闲的空间

SDS与字符串的区别

  C语言使用简单的字符串表示方式,并不能满足Redis对字符串在安全性,效率,以及功能方面的要求,SDS更使用Redis。

@1 常数复杂度获取字符串长度

C字符串:
  因为C语言并不记录自身的长度信息,所以获取一个C字符串的长度,程序必须遍历整个字符串,对遇到的,每个字符进行计数,直到遇到代表字符串结尾的空字符串为止,这个操作的复杂度为O(n)。

SDS:
  与C语言不同的是,SDS结构中的属性length记录了SDS本身的长度,所以获取一个SDS长度的复杂度为O(1)。有人疑问那么SDS的length值是哪来的?这里的length值是SDS API在设置和更新SDS时自动完成的。

总结1:通过使用SDS而不是C字符串,Redis获取字符串长度的复杂度由O(N)降为O(1),这确保了字符串长度的获取的工作不会成为Redis的性能瓶颈。

@ 2杜绝缓冲区溢出

C字符串:
  由于C自身不记录字符串的长度带来一个问题是容易造成缓冲区溢出(buffer overflow)。在<string.h>/strcat函数中,可以将一个字符串拼接到另外一个字符串的末尾。

`char *strcat(char *dest,const char *src)`

  理想状态下,用户在使用这个函数时,假定C为dest分配了足够多的内存,可以容纳src字符串中的所有内容,而一旦这个假定不成立,就会产生缓冲区溢出。举个例子,假定内存中有相邻的两个字符串s1,s2,如图:

    s1                        s2
| |
...|'R'|'e'|'d'|'i'|'s'|'\0'||'g'|'o'|'o'|'d'|'\0'|...

  如果执行strcat(s1," cluster");将Redis改为”Redis cluster“,但是粗心的却忘了在执行这句之前为s1分配足够的空间,那么在执行之后,s1的数据将会溢出到s2所在的空间,导致s2保存的内容意外的被修改。

SDS:

  与C语言不同的是,SDS空间分配政策完全杜绝了发生缓冲区溢出的可能性:当SDS API需要对字符串进行修改时,首先会检查SDS的空间是否满足修改所需的要求,因为SDS自身有对字符串长度记录的属性length和空闲空间属性free,可以借助这两个参数进行检查。SDS会在执行动作之前判断SDS的空间大小,再去执行操作,如果空间不够的话,SDS API会自动扩展空间。

  

@ 3减少修改字符串时带来的内存重分配次数

C字符串:

  因为C字符串不记录自身长度,每次增长或者缩短字符串长度时,程序都要对这个C字符串数组进行一次内存重新分配操作,不然容易造成内存益出。因为内存,分配设计复杂的算法,并且可能需要执行系统调用,所以它通常是一个比较耗时和耗能的操作。但是Redis作为缓存,追求速度,所以不能经常发生内存分配操作。

SDS:

  SDS数组中的未使用空间字节数量由SDS的属性free记录,通过free记录,SDS实现了空间预分配和惰性释放两种优化策略。
1. 空间预分配
  空间预分配用于优化SDS的字符串增长操作:当SDS的API对一个SDS进行修改,并且需要对SDS的空间进行扩展时,程序不仅会为SDS分配修改所需要的空间,而且还会为SDS分配额外的空间。额外的空间分配规则如下:

(1)如果修改SDS之后,SDS的长度小于1MB,那么程序会给SDS分配和length一样大的额外空间,这是SDSlength和free的值相等。举个例子,如果修改后的字符串长度为13k,那么SDS的空间将会占据13+13+1=27k(额外的一个字节用于保存空字符串)。

(2)如果修改SDS之后,SDS的长度大于1MB,那么程序会给SDS分配额外的1MB空间,举个例子,比如修改后的SDS有30MB的大小,那么程序会分配1MB的未使用空间,SDS的buf数组实际大小将是30MB+1MB+1byte。

2.惰性释放

  惰性释放用于优化SDS的字符串缩短操作:当SDS的API要缩短SDS保存的字符串时,程序并不需要立即使用内存重分配策略来回收缩短后多出来的字节,而是使用free属性将这些字节记录起来,并等待使用。

@4 二进制安全

  C字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串末尾之外,字符串里面不能包含空字符串,否则最先被程序读入的空字符串将被误认为是字符串结尾。

SDS API都是二进制安全的,所有SDS API都会以处理二进制的方式来处理存放在SDS buf中的数据,数据写什么样,它被读取时就是什么样子。

@5 兼容部分C字符串函数

  SDS的API总会以SDS保存的数据的末尾设置为空字符串,并且在分配SDS空间时会多分配一个字节的空间来容纳空字符串,这是为了那些保存的数据可以重用一部分<string.h>库中的函数。

总结

字符串和SDS之间的区别总结如下:

原文链接:Redis字符串键的底层原理

redis跳跃表原理

Redis字符串键的底层原理的更多相关文章

  1. Redisson实现Redis分布式锁的底层原理

    一.写在前面 现在面试,一般都会聊聊分布式系统这块的东西.通常面试官都会从服务框架(Spring Cloud.Dubbo)聊起,一路聊到分布式事务.分布式锁.ZooKeeper等知识.所以咱们这篇文章 ...

  2. 从Redis分布式缓存实战入手到底层原理分析、面面俱到覆盖大厂面试考点

    概述 官方说明 Redis官网 https://redis.io/ 最新版本6.2.6 Redis中文官网 http://www.redis.cn/ 不过中文官网的同步更新维护相对要滞后不少时间,但对 ...

  3. Redis五大类型及底层实现原理

    目录 简单动态字符串链表字典跳跃表整数集合压缩列表对象 对象的类型与编码字符串对象列表对象哈希对象 集合对象有序集合对象类型检查与命令多态内存回收对象共享对象的空转时长 简单动态字符串  导读 Red ...

  4. 深入理解Redis主键失效原理及实现机制

    http://blog.jobbole.com/71095/ 对于缓存失效,不同的缓存有不同的处理机制,可以说是大同中有小异,作者通过对Redis 文档与相关源码的仔细研读,为大家详细剖析了 Redi ...

  5. 深入理解Redis主键失效原理及实现机制(转)

    原文:深入理解Redis主键失效原理及实现机制 作为一种定期清理无效数据的重要机制,主键失效存在于大多数缓存系统中,Redis 也不例外.在 Redis 提供的诸多命令中,EXPIRE.EXPIREA ...

  6. Redis主键失效 - 原理及实现机制

    [数据记录过期源码][http://blog.csdn.net/yuanrxdu/article/details/21233047] [http://blog.jobbole.com/71095/] ...

  7. Redis源码分析-底层数据结构盘点

    前段时间翻看了Redis的源代码(C语言版本,Git地址:https://github.com/antirez/redis), 过了一遍Redis数据结构,包括SDS.ADList.dict.ints ...

  8. Redis 的键命令、HyperLogLog 命令、脚本命令、连接命令、服务器命令

    Redis 的键命令.HyperLogLog 命令.脚本命令.连接命令.服务器命令 Redis 的键命令 Redis 的键命令主要用于管理 Redis 的键,如删除键.查询键.修改键及设置某个键等. ...

  9. Redis单机数据库的实现原理

    本文主要介绍Redis的数据库结构,Redis两种持久化的原理:RDB持久化.AOF持久化,以及Redis事件分类及执行原理.最后,分别介绍了单机班Redid客户端和Redis服务器的使用和实现原理. ...

随机推荐

  1. 数据结构之稀疏矩阵C++版

    //只是简单的演示一下,这个实际运用视乎不怎么多,所以java版不再实现 /* 希疏矩阵应用于对数据的压缩,仅仅保留不为0的数据 稀疏矩阵的转置,可以由多种方式,下面演示的稍显简单,时间复杂度略高O( ...

  2. java并发编程(六)----(JUC)Semaphore

    Semaphore,从字面意义上我们知道他是信号量的意思.在java中,一个计数信号量维护了一个许可集.Semaphore 只对可用许可的号码进行计数,并采取相应的行动.拿到信号量的线程可以进入代码, ...

  3. Spring 集成Kafka(完整版)

    前面的文章我们已经完成了Kafka基于Zookeeper的集群的搭建了.Kafka集群搭建请点我.记过几天的研究已经实现Spring的集成了.本文重点 jar包准备 集成是基于spring-integ ...

  4. 心里想的VS嘴上说的

    心里想的VS嘴上说的 背景:昨天开会,在招行总行那边,今天检讨下自己不会说话,真是太难了我! 一.昨日重现 现在回想起当时的场景觉得自己也真是搞笑,这都没死,太难了我.昨天下午在五楼开会,这也是我入职 ...

  5. Jmeter 01 Jmeter下载安装及入门

    jmeter简介 Apache JMeter是Apache组织开发的基于Java的压力测试工具.用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测试领域.--百度百科 下载 下载 ...

  6. 正则表达式之Matcher类中group方法

    前言 同事把一个excel表给我,里面的数据大概有几千的样子吧.自己需要把里面的数据一个一个拿出来做一个http请求,对得到的结果进行过滤,然后再写到上面去.这是就涉及到用脚本来进行操作了,于是自己搞 ...

  7. Git原理入门简析

    为了获得更好的阅读体验,建议访问原地址:传送门 前言: 之前听过公司大佬分享过 Git 原理之后就想来自己总结一下,最近一忙起来就拖得久了,本来想塞更多的干货,但是不喜欢拖太久,所以先出一版足够入门的 ...

  8. 本地VSCode编辑远程服务器文件

    前言 先说下我的场景:服务器搭设了一系列复杂环境,然后需要使用PHP实现某些功能 选这种远程编辑的原因: 首先PHP打死我也不想装(这个现在是出了VB外最惹人厌的语言了) 然后环境比较复杂,本地装下比 ...

  9. 深入研究BufferedInputStream内幕

    目录 1 概述 2 BufferedInputStream源码分析 3 BufferedInputStream在实际场景中,没有太多用处 4 BufferedInputStream唯一使用场景 1 概 ...

  10. Linux设备驱动程序学习----3.模块的编译和装载

    模块的编译和装载 更多内容请参考Linux设备驱动程序学习----目录 1. 设置测试系统 第1步,要先从kernel.org的镜像网站上获取一个主线内核,并安装到自己的系统中,因为学习驱动程序的编写 ...