当前位置: 首页 > 后端技术 > Java

深入理解Redis数据结构——简单的动态字符串sds

时间:2023-04-01 20:16:30 Java

Redis是用ANSIC语言编写的。它是一个高性能的key-value数据库,可用于数据库、缓存和消息中间件。Redis键值对中的键都是字符串类型,键值对中的值也是字符串类型。字符串类型在Redis中仍然被广泛使用。本文主要介绍字符串的数据结构——SimpleDynamicString(简称sds)。sds实现了sds的数据结构:structsdshdr{//buf占用长度intlen;//buf剩余可用长度intfree;//保存字符串数据的地方charbuf[];}结构sdshdr保存了len、free和buf三个属性,分别记录了字符的已使用长度、未使用长度和实际保存字符串的数组。下面是一个新的保存helloworld字符串的sdshdr结构体:structsdshdr{len=5;免费=0;buf="你好\0";}free属性值为0,说明这个sds没有分配未使用的空间。len属性值为5,表示这个sds保存的是一个5字节的字符串。buf属性是一个char类型的数组。数组的前五个字节分别存储'h'、'e'、'l'、'l'、'o'五个字符,最后一个字节存储空字符'\0'。sds遵守C字符串以空串结尾的约定,保存的空串的一个字节空间不计入sds的len属性。诸如在字符串末尾添加一个空字符串等操作都是由sds函数自动完成的,所以这个空字符对用户来说是完全透明的。通过len属性,可以实现时间复杂度为O(1)的长度计算。另外,通过给buf分配一些额外的空间,并使用free记录未使用空间的长度,sdshdr可以减少内存重分配。这是sds相对于c字符串的优势之一。为什么Redis不用C语言来表示字符串Redis是用C语言开发的,但是在使用最多的字符串上,Redis并没有使用C语言传统的字符串表示,而是使用自己构造的简单动态字符串(sds)。在C语言中,一个字符串可以用一个以\0结尾的char数组来表示。例如,helloworld在C语言中可以表示为“helloworld\0”。数组的长度在初始化后一般是固定的,不能支持字符串追加和长度计算操作:每次计算字符串长度都要遍历数组,所以每次追加的时间复杂度为O(N)对字符串进行操作,需要对字符串进行内存分配。sds优化追加字符的操作。Redis作为数据库,对查询速度要求严格,数据修改频繁。如果每次修改一个字符串都需要分配内存,会花费很多时间。所以Redis选择了sds而不是C字符串,sds可以减少追加字符的内存分配。举例来说,执行以下操作时sds内部会发生什么:redis>setmsg"helloworld"OKredis>appendmsg"again"(integer)18redis>getmsg"helloworldagain"首先设置命令为创建并保存helloworld到一个sdshdr,这个sdshdr的值如下:structsdshdr{len=11;免费=0;buf="helloworld\0";}执行append命令时,对应的sdshdr会更新,字符串"again"会追加到原来的"helloworld":structsdshdr{len=17;免费=17;buf="helloworldagain\0";}调用set命令创建sdshdr时,Redis并没有为sdshdr分配额外的空间,free属性为0。执行append操作后,Redis分配了两倍多的空间给sdshdr缓冲区。执行完append命令后,一共需要17+1个字节来保存“helloworldagain”,但是程序为sdshdr分配了17+17+1=35个字节,如果后续对sdshdr进行append操作,就appendbuf的长度没有超过free属性的值,所以不需要重新分配buf的内存。例如命令执行后,buf的内存不会重新分配,因为新追加的字符串长度小于17:redis>appendmsg"again"(integer)23对应的sdshdr结构如下:结构sdshdr{len=23;免费=11;buf="helloworldagainagain\0";}Redis的内存分配可以在源码sds.s/sdsMakeRoomFor中找到,sdsMakeRoomFor函数描述了内存分配的策略,该函数的伪代码如下://sdshdr:beforeappendingcharacter//addlen:addstringsdssdsMakeRoomFor(sdshdr,addlen){//额外空间大于额外空间,乱序重新分配内存,直接返回if(free>=addlen)returns;//计算新字符的长度newlen=(len+addlen);//如果新字符长度小于SDS_MAX_PREALLOC,分配两倍新字符空间//如果新字符长度大于SDS_MAX_PREALLOC,分配新字符空间+SDS_MAX_PREALLOC空间if(newlen