二进制安全是与操作字符串的方法的相关术语,该方法的参数可以包含任何字符,方法会公平的对待数据流的每个字符,不特殊处理其中某一个字符,包括特殊字符。【特殊情况:该方法就是用于处理特定字符】
C语言中字符串是以特殊字符“\0”来作为字符串的结束标识。对于字符串str="0123456789\0123456789”来说,在C语言里面str的长度就是10(strlen(str)=10),所以strlen()函数不是二进制安全的。
简单动态字符串(Simple Dynamic Strings,SDS)是Redis的基本数据结构之一,主要用于存储字符串、整型数字。
Redis 3.2 之前的SDS主要是通过C语言中结构体类型确定的,包括已占用字节数len、剩余可用字节数free、实际保存字符串的字符数组来确定的。 (1)由于有长度的统计变量len的存在,读写字符串时不依赖“\0”终止符,保证了二进制安全 (2)Redis保存的字符串对外暴露的是数组的长度指针,而不是结构体的指针,上层可以像操作普通字符串一样操作SDS。 struct sdshdr { //buf中已占用字节数 int len; //buf剩余可用字节数 int free; //实际保存字符串的字符数组 char buf[]; };柔性数组:C语言中结构体的最后一个元素可以是大小未知的数组,也就是所谓的0长度,所以我们可以用结构体来创建柔性数组。而定义SDS的结构体中的数组就是柔性数组,可以根据需要调整数组长度,也可以通过柔性数组的首地址偏移得到结构体首地址。 SDS采用柔性数组的缺点: 不同SDS字符串占用了相同大小的头部空间(buf、leng长度),浪费空间。
Redis 5.0改进了SDS,将根据字符串的长度,分成了5种类型sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64。图中: 1、len表示buf中已占用字节数。 2、alloc表示buf中已分配字节数,记录的是为buf分配的总长度,不同于free。 3、flags标识当前结构体的类型,低3位用作标识位,高5位预留。 4、buf柔性数组,真正存储字符串的数据空间。
(1)五种类型都多了一个flags字段,但sdsdr5没有了头部(len和free ) (2)sdshdr5结构中,flags占1个字符,其低3位表示结构体类型,高5位表示长度,能表示的长度区间为0~31,flags后面就是字符串的内容。而长度大于31的字符串,1个字节存不下,那么就要将len和free单独存放,因此redis存放数据时会先检查字符串长度,再根据字符串长度计算好不同类型的头部和初始长度,然后动态分配内存
(1)SDS如何兼容C语言字符串?如何保证二进制安全? SDS对象中的buf是一个柔性数组,上层调用时,SDS直接返回了buf。由于buf是直接指向内容的指针,所以兼容C语言函数。而当真正读取内容时,SDS会通过len来限制读取长度,而非“0”,所以保证了二进制安全。 (2)sdshdr5的特殊之处是什么? sdshdr5只负责存储小于32字节的字符串。一般情况下,小字符串的存储更普遍,所以Redis进一步压缩了sdshdr5的数据结构,将sdshdr5的类型和长度放入了同一个属性中,用flags的低3位存储类型,高5位存储长度。创建空字符串时,sdshdr5会被sdshdr8替代。 (3)SDS是如何扩容的? SDS在涉及字符串修改时会调用sdsMakeroomFor函数进行检查,会根据空闲长度和新增内容的长度进行比较判断,然后根据不同情况动态扩容