avatar

目录
Redis数据类型(1)—— 字符串

Redis一共有五种常用的数据类型,首先让我们从最常见的字符串类型说起

1. Redis 为什么抛弃C字符串

众所周知,Redis是用C语言编写而成的,但是它的字符串实现方式并不是直接套用C语言中的实现,在C语言中,字符串是以字符数组来存储的,并且每个数组的最后都有一个 ‘\0’ 来作为结束标识。Redis不采用C字符串,主要是因为C字符串有以下一些不足:

  1. C字符串并不直接记录字符串的自身长度,因此当想要获取字符串长度时,总是需要遍历数组,O(N)的时间复杂度是C字符串的其中一个劣势。
  2. 同样地,因为C字符串不记录自身长度,还可能造成在执行某些标准库函数比如<string.h>/strcat时,产生缓冲区溢出的问题。
  3. 由于C字符串底层其实是一个长度为N+1的数组,所以每次对字符串执行append或者trim操作时,实际上是会重新分配数组内存空间的。一般情况下其实该问题不大,但是在Redis的很多应用场景中,有很多地方会对字符串进行频繁地修改,从而由于频繁地申请内存而带来一些性能问题。

正是由于C字符串存在这么一些不足,因此Redis才决定另辟蹊径,其作者自己重新设计了字符串的底层数据结构,也就是我们所说的SDS,即 Simple Dynamic String———简单动态字符串。

2. SDS的具体结构

image

  • buf:字节数组,保存实际数据。为了表示字节数组的结束,Redis 会自动在数组最后加一个“\0”,这就会额外占用 1 个字节的开销。
  • len:占 4 个字节,表示 buf 的已用长度。
  • alloc:也占个 4 字节,表示 buf 的实际分配长度,一般大于 len. (从Redis 3.2.0开始使用该字段表示已分配的数据长度,之前的版本使用free表示未分配的数组字节长度)

3. Redis字符串对象

Redis使用对象来表示数据库中的键和值。对于每一个新创建的键值对,都至少会创建两个对象,举个栗子:执行以下操作

Code
1
set msg  'hello world'

会同时创建两个Redis对象。一个是键,即包含了字符串值 “msg” 的字符串对象,另一个是值,是一个包含了字符串值”hello world”的对象。

每个Redis 对象都会由一个redisObject的结构体来表示, 其中包含有以下这么些字段:

c
1
2
3
4
5
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
void *ptr;
}

其中头8个字节(type+encoding)是元数据,最后的 *ptr 是一个8字节的指针,指向具体的数据存储内存地址。
type记录了对象的类型,而encoding则记录了相应类型的编码方式,编码方式决定了存储数据时应该采用哪一种底层数据结构。

对于字符串对象而言,编码方式主要是由以下几种

  • int
  • embstr
  • raw
1. int类型编码

从节约内存的角度出发,如果保存的字符串类型是长整型整数时,redis不会分配额外的SDS来保存该整数,而是直接将原本指向SDS的指针直接赋值为整型数据。

2. embstr类型编码

当保存的字符串数据超过8KB且未超过44KB时,此时RedisObject 中的元数据、指针和SDS是一块连续的内存区域,这样做的好处是避免了产生内存碎片。

3. raw类型编码

当保存的字符串数据量超过44KB时,SDS所需的内存空间就不再跟RedisObject布局在一起,而是选择另外独立分配内存空间了,并最终用ptr指针指向数据存储的实际内存地址。这种内存结构布局就是所谓的raw编码方式。

用一张图总结如下:

image

文章作者: JanGin
文章链接: http://jangin.github.io/2021/04/16/datatype-in-redis-1-string/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 JanGin's BLOG

评论