手写Redis基本结构-动态字符串SDS

5 篇文章 0 订阅
4 篇文章 0 订阅

Redis中存字符串,是实际使用场景中最常用的方式,但是redis并没有直接使用C语言中传统的字符串表示,而是构建了一种名为简单动态字符串(SDS)的抽象类型,本文带你利用Java实现SDS基本结构。

1、SDS定义

我们首先看一下SDS的字符串的整体结构如下图:
SDS基本结构
所以,我们首先创建一个包含free、len和char类型数组buf,代码如下:

public class SDS {
    private static char endChar= '\0';
    //字符串长度
    private int len;
    //buf数组中未使用的字节数
    private int free;
    //字节数组
    private char[] buf;

    //构造函数
    public SDS(String str){
        this.len = str.length();
        this.buf = Arrays.copyOf(str.toCharArray(),this.len+1);
        this.buf[len] = endChar;
        this.free = buf.length - len - 1;
    }
    //-----------省略set&get--------
}

类SDS自带有参构造函数,用于对sds赋值时进行初始化,为了保证sds的字符串符合传统C语言对字符串的存储,在buf数组后会添加 '\0’来代表字符串的结尾,用一下代码测试一下SDS的基本结构:

public class TestRedis {
       public static void main(String[] args) {
        SDS sds = new SDS("a a");
        char[] a = sds.getBuf();
        for(char aTemp:a){
            System.out.println(aTemp);
        }
    }
}

输出结果如下:
在这里插入图片描述
从结果中我们发现,字符串中自带的空格和最后结尾增加的\0在控制台输出,都是空格,这是不是就代表如果字符串中存在空格,就会被误认为是字符串的结尾标志?基于此疑问,我们对代码进行了测试:

public class TestRedis {
    private static char test = '\0';
    public static void main(String[] args) {
        SDS sds = new SDS("a a");
        char[] a = sds.getBuf();
        for(char aTemp:a){
            System.out.println("--------");
            System.out.println(aTemp == test);
        }
    }
}

测试结果如下:
在这里插入图片描述
我们发现,即使字符串中携带空格,也不会被当成字符串的结尾标志。

2、SDS和C字符串的区别

根据SDS的基本结构,我们可以对比一下SDS和C字符串的区别:

2.1 常量级获取字符串长度

SDS中存在私有属性len,用来表示buf中除去\0之外,字符串的长度,所以SDS获取字符串长度的时间复杂度是常数级O(1)的,而C语言中字符串是通过遍历字符串获取,其时间复杂度为O(N)

2.2 避免缓存区溢出

在C语言中,当对字符串进行修改时,如果没有分配足够多的内存,就会导致缓冲区溢出,但是SDS会先检查SDS的空间是否满足修改所需的要求,如果不满足,会自动对SDS进行扩容,然后在进行实际的修改操作;

2.3 内存重分配

在SDS中存在free空间,SDS通过free空间,实现了空间预分配和惰性释放两种优化策略,来实现内存重分配;

2.3.1 空间预分配

当对SDS进行修改时,会通过设置合理的free空间,来减少SDS内存分配的次数,从而增加效率,SDS对free空间分配应该符合以下条件:

1、如果修改后的SDS长度小于1MB(1 (2^20)B),那么free空间=len大小,buf的实际长度为2len+1;
2、如果修改后的SDS长度大于1MB(1 * (2^20)B),那么free空间=1MB,buf的实际长度为1MB+len+1;

我们通过实现字符串拼接功能sdscat(),来实现空间预分配策略。

//拼接字符串
    public void sdscat(String str){
        char[] strTemp = str.toCharArray();
        int strTempLen = str.length();
        int lastLen = this.len + strTempLen;
        //先判断长度
        if(lastLen < 1 * Math.pow(2,20)){
            //小于1MB(2^20B),那么free空间=len大小,buf的实际长度为2*len+1
            this.free = lastLen;
        }else{
            //大于1MB(2^20B),那么free空间=1MB,buf的实际长度为1MB+len+1
            this.free = (int)Math.pow(2,20);
        }
        this.len = lastLen;
        //拼接数组
        char[] originChar = this.toString().toCharArray();
        char[] result = Arrays.copyOf(originChar,lastLen);
        System.arraycopy(strTemp,0,result,originChar.length,strTemp.length);
        this.buf = Arrays.copyOf(result,lastLen+1);
        this.buf[lastLen] = endChar;
    }
    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder("");
        for(int i=0;i<this.buf.length;i++){
            if(this.buf[i] != '\0'){
                stringBuilder.append(this.buf[i]);
            }
        }
        return stringBuilder.toString();
    }
2.3.2 惰性空间释放

上一小节,空间预分配讲的的是free空间如何创建,而惰性空间释放讲的其实是free空间怎么用,既然我们创建了对于的free空间,那么如何使用呢?
在对SDS进行增加操作时,会优先使用free空间,如果free空间足够当前新增操作的字符串,则直接分配,不用执行内存的重新分配,如果free空间不足之处,则会按照2.3.1中提到的规则进行空间分配,根据此,我们对字符串拼接sdscat函数进行完善如下:

public void sdscat(String str){
        char[] strTemp = str.toCharArray();
        int strTempLen = str.length();
        if(strTempLen>this.free){
            int lastLen = this.len + strTempLen;
            //先判断长度
            if(lastLen < 1 * Math.pow(2,20)){
                //小于1MB(2^20B),那么free空间=len大小,buf的实际长度为2*len+1
                this.free = lastLen;
            }else{
                //大于1MB(2^20B),那么free空间=1MB,buf的实际长度为1MB+len+1
                this.free = (int)Math.pow(2,20);
            }
            this.len = lastLen;
            //拼接数组
            char[] originChar = this.toString().toCharArray();
            char[] result = Arrays.copyOf(originChar,lastLen);
            System.arraycopy(strTemp,0,result,originChar.length,strTemp.length);
            this.buf = Arrays.copyOf(result,lastLen+1);
            this.buf[lastLen] = endChar;
        }else{
            //当free空间足够分配
            char[] originChar = this.toString().toCharArray();
            char[] result = Arrays.copyOf(originChar,this.len + this.free);
            System.arraycopy(strTemp,0,result,originChar.length,strTemp.length);
            this.buf[strTempLen+this.len] = endChar;
            this.free = this.free - strTempLen;
            this.len = this.len + strTempLen;
        }

    }

自此,SDS介绍完毕,如有问题,欢迎留言交流,谢谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值