Redis的字符串底层是啥?为了速度和安全做了啥?
面试场景
面试官:Redis有哪些数据类型?
我:String,List,set,zset,hash
面试官:没了?
我:哦哦哦,还有HyperLogLog,bitMap,GeoHash,BloomFilter
面试官:就这?回家等通知吧。

前言
我敢肯定,第一个回答,100%的人都能说上来,但是第二个回答能回答上来的人可能就不多了,但是这也不是我今天探讨的话题。
我就从我自己的去面试的回答思路,以及作为一个面试官他想听到的标准答案来给大家出一期,Redis基础类型的文章(系列文章),写这个的时候我还是很有心得的,不知道大家有多少人跟我最开始一样,面试官问有哪些类型,就回答出那五种就结束了,如果你是这样的可以在评论区留言,让我看看有多少人是这样的。
但是,一场面试少说都是半小时起步上不封顶,你这样一句话就回答了这么重要的五个知识点,这个结果是你想要的么?是面试官想要的么?
我再问你一个问题,你可能就懵逼了:String在Redis底层是怎么存储的?这些数据类型在Redis中是怎么存放的?Redis快的原因就只有单线程和基于内存么?

宝贝,触及知识盲区没?不慌,我以前也是这样的,我以为我背出那五种就完事了,结果被面试官安排了一波,后面我苦心修炼,总算是好了一点,现在对缓存也是非常熟悉了,你不会没事,有我嘛,乖。
正文
Redis是C语言开发的,C语言自己就有字符类型,但是Redis却没直接采用C语言的字符串类型,而是自己构建了动态字符串(SDS)
的抽象类型。

就好比这样的一个命令,其实我是在Redis创建了两个SDS,一个是名为aobing
的Key SDS,另一个是名为cool
的Value SDS,就算是字符类型的List,也是由很多的SDS构成的Key和Value罢了。
SDS在Redis中除了用作字符串,还用作缓冲区(buffer),那到这里大家都还是有点疑惑的,C语言的字符串不好么为啥用SDS?SDS长啥样?有什么优点呢?
为此我去找到了Redis的源码,可以看到SDS值的结果大概是这样的,源码的在GitHub上是开源的大家一搜就有了。
struct sdshdr{
int len;
int free;
char buf[];
}

回到最初的问题,为什么Redis用了自己新开发的SDS,而不用C语言的字符串?那好我们去看看他们的区别。
SDS与C字符串的区别
计数方式不同
C语言对字符串长度的统计,就完全来自遍历,从头遍历到末尾,直到发现空字符就停止,以此统计出字符串的长度,这样获取长度的时间复杂度来说是0(n),大概就像下面这样:

但是这样的计数方式会留下隐患,所以Redis没有采用C的字符串,我后面会提到。
而Redis我在上面已经给大家看过结构了,他自己本身就保存了长度的信息,所以我们获取长度的时间复杂度为0(1),是不是发现了Redis快的一点小细节了?还没完,不止这些。
杜绝缓冲区溢出
字符串拼接是我们经常做的操作,在C和Redis中一样,也是很常见的操作,但是问题就来了,C是不记录字符串长度的,一旦我们调用了拼接的函数,如果没有提前计算好内存,是会产生缓存区溢出的。
比如本来字符串长这样:

你现在需要在后面拼接 ,但是你没计算好内存,结果就可能这样了:

这是你要的结果么?很显然,不是,你的结果意外的被修改了,这要是放在线上的系统,这不是完了?那Redis是怎么避免这样的情况的?
我们都知道,他结构存储了当前长度,还有free未使用的长度,那简单呀,你现在做了拼接操作,我去判断一些是否可以放得下,如果长度够就直接执行,如果不够,那我就进行扩容。
这些大家在Redis源码里面都是可以看到对应的API的,后面我就不一一贴源码了,有兴趣的可以自己去看一波,需要一点C语言的基础。

减少修改字符串时带来的内存重分配次数
C语言字符串底层也是一个数组,每次创建的时候就创建一个N+1长度的字符,多的那个1,就是为了保存空字符的,这个空字符也是个坑,但是不是这个环节探讨的内容。
Redis是个高速缓存数据库,如果我们需要对字符串进行频繁的拼接和截断操作,如果我们写代码忘记了重新分配内存,就可能造成缓冲区溢出,以及内存泄露。
内存分配算法很耗时,且不说你会不会忘记重新分配内存,就算你全部记得,对于一个高速缓存数据库来说,这样的开销也是我们应该要避免的。
Redis为了避免C字符串这样的缺陷,就分别采用了两种解决方案,去达到性能最大化,空间利用最大化:
空间预分配:当我们对SDS进行扩展操作的时候,Redis会为SDS分配好内存,并且根据特定的公式,分配多余的free空间,还有多余的1byte空间(这1byte也是为了存空字符),这样就可以避免我们连续执行字符串添加所带来的内存分配消耗。
比如现在有这样的一个字符:

我们调用了拼接函数,字符串边长了,Redis还会根据算法计算出一个free值给他备用:

我们再继续拼接,你会发现,备用的free用上了,省去了这次的内存重分配:

惰性空间释放:刚才提到了会预分配多余的空间,很多小伙伴会担心带来内存的泄露或者浪费,别担心,Redis大佬一样帮我们想到了,当我们执行完一个字符串缩减的操作,redis并不会马上收回我们的空间,因为可以预防你继续添加的操作,这样可以减少分配空间带来的消耗,但是当你再次操作还是没用到多余空间的时候,Redis也还是会收回对于的空间,防止内存的浪费的。
还是一样的字符串:

当我们调用了删减的函数,并不会马上释放掉free空间:

如果我们需要继续添加这个空间就能用上了,减少了内存的重分配,如果空间不需要了,调用函数删掉就好了:

二进制安全
仔细看的仔肯定看到上面我不止一次提到了空字符也就是’\0‘,C语言是判断空字符去判断一个字符的长度的,但是有很多数据结构经常会穿插空字符在中间,比如图片,音频,视频,压缩文件的二进制数据,就比如下面这个单词,他只能识别前面的 不能识别后面的字符,那对于我们开发者而言,这样的结果显然不是我们想要的对不对。

Redis就不存在这个问题了,他不是保存了字符串的长度嘛,他不判断空字符,他就判断长度对不对就好了,所以redis也经常被我们拿来保存各种二进制数据,我反正是用的很high,经常用来保存小文件的二进制。

资料参考:Redis设计与实现
总结
大家是不是发现,一个小小的SDS居然有这么多道理在这?
以前就知道Redis快,最多说个Redis是单线程的,说个多路IO复用,说个基于内存的操作就完了,现在是不是还可以展开说说了?
本文是系列文的第一章,后续会陆续更新的,不知道这样的类型大家是否喜欢,可以留言给我反馈。
大家一同去面试,一样的问题,就是有人能过,有人不能过,大家经常归咎于自己学历,自己过往经历的原因,但是你可以问一下自己,底层的细节字节是否有深究呢?细节往往才是最重要的,也是最少人知道的,如何和别的仔拉开差距拿到offer,我想就是这样些细节决定的吧,背谁不会呢?
絮叨
另外,敖丙把自己的面试文章整理成了一本电子书,共 1630页!目录如下

现在免费送给大家,在我的公众号三太子敖丙回复 【888】 即可获取。

我是敖丙,一个在互联网苟且偷生的程序员。
你知道的越多,你不知道的越多,人才们的 【三连】 就是丙丙创作的最大动力,我们下期见!
注:如果本篇博客有任何错误和建议,欢迎人才们留言!
文章持续更新,可以微信搜索「 三太子敖丙 」第一时间阅读,回复【资料】有我准备的一线大厂面试资料和简历模板,本文 GitHub https://github.com/JavaFamily 已经收录,有大厂面试完整考点,欢迎Star。

Redis的字符串底层是啥?为了速度和安全做了啥?的更多相关文章
- 深入了解Redis(1)-字符串底层实现
一.简单动态字符串(SDS) Redis中字符串实现有两种方式,C语言传统字符串(以空字符结尾的字符数组)和简单动态字符串(SDS),并将SDS作为默认字符串表示. C字符串只会作为字符串字面量,用在 ...
- 【Redis面试题】Redis的字符串是怎么实现的?
年前本人在找工作面试时在Redis相关问题上可栽了跟头.在面试前按常规套路准备了一下,比如 Redis 的常用5种数据结构,Redis持久化策略,Redis实现分布式锁,简单发布订阅等等都准备了,当时 ...
- Redis 数据结构-字符串源码分析
相关文章 Redis 初探-安装与使用 Redis常用指令 本文将从以下几个部分进行介绍 1.前言 2.常用命令 3.字符串结构 4.字符串实现 5.命令是如果操作字符串的 前言 平时在使用 Redi ...
- redis之字符串命令源代码解析(二)
形象化设计模式实战 HELLO!架构 redis命令源代码解析 在redis之字符串命令源代码解析(一)中讲了get的简单实现,并没有对 ...
- 高性能的Redis之对象底层实现原理详解
对象 在前面的数个章节里, 我们陆续介绍了 Redis 用到的所有主要数据结构, 比如简单动态字符串(SDS).双端链表.字典.压缩列表.整数集合, 等等. Redis 并没有直接使用这些数据结构来实 ...
- Redis操作字符串工具类封装,Redis工具类封装
Redis操作字符串工具类封装,Redis工具类封装 >>>>>>>>>>>>>>>>>>& ...
- redis数据类型-字符串类型
Redis数据类型 字符串类型 字符串类型是Redis中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据.你可以用其存储用户的邮箱.JSON化的对象甚至是一张图片.一个字符串类型键允许存储的 ...
- Redis 操作字符串数据
Redis 操作字符串数据: > set name "Tom" // set 用于添加 key/value 数据,如果 key 存在则覆盖 OK > setnx nam ...
- 第二百九十五节,python操作redis缓存-字符串类型
python操作redis缓存-字符串类型 首先要安装redis-py模块 python连接redis方式,有两种连接方式,一种是直接连接,一张是通过连接池连接 注意:以后我们都用的连接池方式连接,直 ...
随机推荐
- C/S C# WPF锐浪报表教程
前言:锐浪报表是一种中国式报表的报表开发工具.博主使用锐浪报表有一段时间了,积累了一些经验希望能帮助你快速掌握并使用 第一章:集成项目 首先我们先去锐浪报表官网下载并安装锐浪报表. 创建WPF应用程序 ...
- WeChair项目Alpha冲刺(10/10)
团队项目进行情况 1.昨日进展 Alpha冲刺第十天 昨日进展: 前端:安排页面美化,设计实名认证 后端:更新dao层代码 数据库:修改数据表属性,与后端部署数据库交互 2.今日安排 前端:继续 ...
- Mac上使用brew另装ruby和gem的新玩法
[本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] 众所周知,Mac ...
- JavaWeb网上图书商城完整项目--day02-28.查询所有分类功能之left页面使用Q6MenuBar组件显示手风琴式下拉菜单
首先页面去加载的时候,会去加载main.js文件,我们在加载left.jsp.top.jsp body.jsp,现在我们修改main.jsp的代码,让它去请求的时候去访问的是不在直接去访问left.j ...
- Python变量与基本数据类型
Python变量与基本数据类型 前言 好了,从本章开始将正式进入Python的学习阶段.本章主要介绍的是Python变量与基本数据类型的认识,这些都是最基本的知识并且必须要牢靠掌握在心中. 注释 学习 ...
- python将列表按行写入csv
import csv rows2 = ['abc1/ab1c','N'] for n in range(10): f = open("ok.csv", 'a',newline='' ...
- keras中loss与val_loss的关系
loss是训练集的损失值,val_loss是测试集的损失值 以下是loss与val_loss的变化反映出训练走向的规律总结: train loss 不断下降,test loss不断下降,说明网络仍在学 ...
- IOC和DI的概念,以及Spring框架的介绍
对于Java开发者来说,Spring肯定是一个避不开的技术.所以准备系统的学下Spring框架. 我给自己设计的学习路线是这样的:首先阅读下Spring的官方文档(注意Spring官网上有很多项目,S ...
- 洛谷 P1215 【[USACO1.4]母亲的牛奶 Mother's Milk】
这道题\(DFS\)就好了,六种情况,\(ab,ac,ba,bc,ca,cb\),我们直接枚举就可.什么?这样不会结束?用一个\(vis\)数组判断走过没有就可以了.最后排序输出即可. \(code: ...
- abp审计日志功能的关闭
参考官网介绍:https://aspnetboilerplate.com/Pages/Documents/Audit-Logging