阿里云服务器ECS    
弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新 [咨询更多]
阿里云存储OSS
简单易用、多重冗余、数据备份高可靠、多层次安全防护安全性更强、低成本 [咨询更多]
阿里云数据库RDS
稳定可靠、可弹性伸缩、更拥有容灾、备份、恢复、监控、迁移等方面的全套解决方案 [咨询更多]
阿里云安全产品
DDoS高防IP、web应用防火墙、安骑士、sll证书、态势感知众多阿里云安全产品热销中 [咨询更多]
阿里云折扣优惠    
云服务器ECS、数据库、负载均衡等产品新购、续费、升级联系客服获取更多专属折扣 [咨询更多]
Redis 5 创建字符串、释放字符串、拼接字符串、其余API
2020-8-21    点击量:
  数据结构的基本操作不外乎增、删、改、查,SDS也不例外。由于Redis 3.2后的SDS涉及多种类型,修改字符串内容带来的长度变化可能会影响SDS的类型而引发扩容。本节着重介绍创建、释放、拼接字符串的相关API,帮助大家更好地理解SDS结构。
  1、创建字符串
  Redis通过sdsnewlen函数创建SDS。在函数中会根据字符串长度选择合适的类型,初始化完相应的统计值后,返回指向字符串内容的指针,根据字符串长度选择不同的类型:
  sds sdsnewlen(const void*init,size_t initlen){
  void*sh;
  sds s;
  char type=sdsReqType(initlen);//根据字符串长度选择不同的类型
  if(type==SDS_TYPE_5&&initlen==0)type=SDS_TYPE_8;//SDS_TYPE_5强制转化为SDS_TYPE_8
  int hdrlen=sdsHdrSize(type);//计算不同头部所需的长度unsigned char*fp;/*指向flags的指针*/
  sh=s_malloc(hdrlen+initlen+1);//"+1"是为了结束符''
  ...
  s=(char*)sh+hdrlen;//s是指向buf的指针
  fp=((unsigned char*)s)-1;//s是柔性数组buf的指针,-1即指向flags...
  s[initlen]='';//添加末尾的结束符
  return s;
  }

  注意Redis 3.2后的SDS结构由1种增至5种,且对于sdshdr5类型,在创建空字符串时会强制转换为sdshdr8。原因可能是创建空字符串后,其内容可能会频繁更新而引发扩容,故创建时直接创建为sdshdr8。
  创建SDS的大致流程:首先计算好不同类型的头部和初始长度,然后动态分配内存。需要注意以下3点。
  1)创建空字符串时,SDS_TYPE_5被强制转换为SDS_TYPE_8。
  2)长度计算时有“+1”操作,是为了算上结束符“”。
  3)返回值是指向sds结构buf字段的指针。返回值sds的类型定义如下:
  typedef char*sds;
  从源码中我们可以看到,其实s就是一个字符数组的指针,即结构中的buf。这样设计的好处在于直接对上层提供了字符串内容指针,兼容了部分C函数,且通过偏移能迅速定位到SDS结构体的各处成员变量。
  2、释放字符串
  SDS提供了直接释放内存的方法——sdsfree,该方法通过对s的偏移,可定位到SDS结构体的首部,然后调用s_free释放内存:
  void sdsfree(sds s){
  if(s==NULL)return;
  s_free((char*)s-sdsHdrSize(s[-1]));//此处直接释放内存
  }

  为了优化性能(减少申请内存的开销),SDS提供了不直接释放内存,而是通过重置统计值达到清空目的的方法——sd-sclear。该方法仅将SDS的len归零,此处已存在的buf并没有真正被清除,新的数据可以覆盖写,而不用重新申请内存。
  void sdsclear(sds s){
  sdssetlen(s,0);//统计值len归零
  s[0]='';//清空buf
  }

  3、拼接字符串
  拼接字符串操作本身不复杂,可用sdscatsds来实现,代码如下:
  sds sdscatsds(sds s,const sds t){return sdscatlen(s,t,sdslen(t));
  }

  sdscatsds是暴露给上层的方法,其最终调用的是sdscatlen。由于其中可能涉及SDS的扩容,sdscatlen中调用sdsMake-RoomFor对带拼接的字符串s容量做检查,若无须扩容则直接返回s;若需要扩容,则返回扩容好的新字符串s。函数中的len、curlen等长度值是不含结束符的,而拼接时用memcpy将两个字符串拼接在一起,指定了相关长度,故该过程保证了二进制安全。最后需要加上结束符。
  /*将指针t的内容和指针s的内容拼接在一起,该操作是二进制安全的*/
  sds sdscatlen(sds s,const void*t,size_t len){
  size_t curlen=sdslen(s);
  s=sdsMakeRoomFor(s,len);
  if(s==NULL)return NULL;
  memcpy(s+curlen,t,len);//直接拼接,保证了二进制安全sdssetlen(s,curlen+len);
  s[curlen+len]='';//加上结束符
  return s;
  }

  图2-5描述了sdsMakeRoomFor的实现过程。
  Redis的sds中有如下扩容策略。
  1)若sds中剩余空闲长度avail大于新增内容的长度ad-dlen,直接在柔性数组buf末尾追加即可,无须扩容。代码如下:
  sds sdsMakeRoomFor(sds s,size_t addlen){
  void*sh,*newsh;
  size_t avail=sdsavail(s);
  size_t len,newlen;
  char type,oldtype=s[-1]&SDS_TYPE_MASK;//s[-1]即flags
  int hdrlen;
  if(avail>=addlen)return s;//无须扩容,直接返回
  ...
  }

  2)若sds中剩余空闲长度avail小于或等于新增内容的长度addlen,则分情况讨论:新增后总长度len+addlen<1MB的,按新长度的2倍扩容;新增后总长度len+addlen>1MB的,按新长度加上1MB扩容。代码如下:
  sds sdsMakeRoomFor(sds s,size_t addlen){...
  newlen=(len+addlen);
  if(newlen<SDS_MAX_PREALLOC)//SDS_MAX_PREALLOC这个宏的值是1MB
  newlen*=2;
  else
  newlen+=SDS_MAX_PREALLOC;
  ...
  }

  3)最后根据新长度重新选取存储类型,并分配空间。此处若无须更改类型,通过realloc扩大柔性数组即可;否则需要重新开辟内存,并将原字符串的buf内容移动到新位置。具体代码如下:

sdsMake RoomFor的实现过程sds

  图2-5 sdsMake RoomFor的实现过程sds
  sdsMakeRoomFor(sds s,size_t addlen)
  {
  ...
  type=sdsReqType(newlen);
  /*type5的结构不支持扩容,所以这里需要强制转成type8*/
  if(type==SDS_TYPE_5)type=SDS_TYPE_8;
  hdrlen=sdsHdrSize(type);
  if(oldtype==type){
  /*无须更改类型,通过realloc扩大柔性数组即可,注意这里指向buf的指针s被更新了*/newsh=s_realloc(sh,hdrlen+newlen+1);
  if(newsh==NULL)return NULL;
  s=(char*)newsh+hdrlen;
  }else{
  /*扩容后数据类型和头部长度发生了变化,此时不再进行realloc操作,而是直接重新开辟内存,拼接完内容后,释放旧指针*/
  newsh=s_malloc(hdrlen+newlen+1);//按新长度重新开辟内存
  if(newsh==NULL)return NULL;
  memcpy((char*)newsh+hdrlen,s,len+1);//将原buf内容移动到新位置
  s_free(sh);//释放旧指针
  s=(char*)newsh+hdrlen;//偏移sds结构的起始地址,得到字符串起始地址
  s[-1]=type;//为falgs赋值
  sdssetlen(s,len);//为len属性赋值}
  sdssetalloc(s,newlen);//为alloc属性赋值
  return s

  ;

        }

  4、其余API
  SDS还为上层提供了许多其他API,篇幅所限,不再赘述。表2-1列出了其他常用的API,读者可自行查阅源码学习,学习时把握以下两点。
  1)SDS暴露给上层的是指向柔性数组buf的指针。

  2)读操作的复杂度多为O(1),直接读取成员变量;涉及修改的写操作,则可能会触发扩容。

   表2-1其他常用的API

表2-1其他常用的API

  SDS的以下特性是如何实现的。
  1)SDS如何兼容C语言字符串?
  如何保证二进制安全?SDS对象中的buf是一个柔性数组,上层调用时,SDS直接返回了buf。由于buf是直接指向内容的指针,故兼容C语言函数。而当真正读取内容时,SDS会通过len来限制读取长度,而非“”,保证了二进制安全。
  2)sdshdr5的特殊之处是什么?
  sdshdr5只负责存储小于32字节的字符串。一般情况下,小字符串的存储更普遍,故Redis进一步压缩了sdshdr5的数据结构,将sdshdr5的类型和长度放入了同一个属性中,用flags的低3位存储类型,高5位存储长度。创建空字符串时,sdshdr5会被sdshdr8替代。
  3)SDS是如何扩容的?
  SDS在涉及字符串修改处会调用sdsMakeroomFor函数进行检查,根据不同情况动态扩容,该操作对上层透明。
联系客服免费领取更多阿里云产品新购、续费升级折扣,叠加官网活动折上折更优惠