tiny-redis SDS实现

基于Redis 设计与实现实现的redis,根据书上的API描述,自己实现C++版本的redis。

SDS是redis内部自己实现的数据结构,相较于c的char *,扩宽了内存大小,减少了对内存的申请,更加迅速。

上代码

#ifndef SDS_H
#define SDS_H

#include <cstddef>
#include <cstring>

#define SDS_MAX_PREALLOC (1024*1024) // 预分配的最大空间

class SDS {
private:

    size_t m_len=0; // 字符串的长度
    size_t m_free=0; // 字符串剩余的可用空间

    void realloc(size_t newlen){
        // 表示现在我需要newlen长度的空间了,你能不能行
        if(newlen<m_free+m_len){
            return ; //可以
        }
        //不可以,重新分配空间
        char *newstr;
        if(newlen>SDS_MAX_PREALLOC){
            newstr=new char[newlen+SDS_MAX_PREALLOC+1];
            m_free=newlen+SDS_MAX_PREALLOC - m_len;
        }else{
            newstr=new char[(newlen<<1) + 1];
            m_free=newlen*2 - m_len;
        }
        memcpy(newstr,m_str,m_len);
        delete[] m_str;
        m_str=newstr;
        
    }; 

    void concatenate(const char* cString, size_t len){
        realloc(m_len+len);
        memcpy(m_str+m_len,cString,len);
        m_len+=len;
        m_str[m_len]='\0';
        m_free=m_free-len;
    }

    void resize(size_t newlen){
        if(newlen<m_len){
            newlen=m_len;
        }
        newlen++;
        char *newstr=new char[newlen+1];
        memcpy(newstr,m_str,newlen);
        newstr[newlen]='\0';
        delete[] m_str;
        m_str=newstr;
        m_free=newlen-m_len;

    }
public:
    char* m_str=new char[0]; // 保存字符串内容的指针

    SDS(const char *cString):SDS(cString,strlen(cString)){

    }

    SDS(const char* cString , size_t len){
        realloc(len);
        //复制
        m_len=len;
        m_free-=m_len;
        memcpy(m_str,cString,m_len);
        m_str[m_len]='\0';
    };

    ~SDS(){
        delete[] m_str;
    }

    // 创建一个包含给定 C 字符串的 SDS
    static SDS create(const char* cString){
        return SDS(cString,strlen(cString));
    };

    // 创建一个不包含任何内容的空 SDS
    static SDS createEmpty(){
        return SDS("",0);
    };



    // 返回 SDS 的已使用空间字节数
    size_t length() const{
        return m_len;
    };

    // 返回 SDS 的未使用空间字节数
    size_t availableSpace() const{
        return m_free;
    };

    const char* data() const{
        return m_str;
    };

    // 创建一个给定 SDS 的副本(copy)
    SDS duplicate() const{
        return SDS(m_str,m_len);
    };

    // 清空 SDS 保存的字符串内容
    void clear(){
        m_free+=m_len;
        m_len=0;
    };


    // 将给定 C 字符串拼接到 SDS 字符串的末尾
    void concatenate(const char* cString){
        concatenate(cString,strlen(cString));
    };

    // 将给定 SDS 字符串拼接到另一个 SDS 字符串的末尾
    void concatenate(const SDS& sds){
        concatenate(sds.m_str,sds.m_len);
    };

    // 将给定的 C 字符串复制到 SDS 里面, 覆盖 SDS 原有的字符串
    void copy(const char* cString){
        clear();
        concatenate(cString);
    };

    // 用空字符将 SDS 扩展至给定长度
    void growZero(size_t length){
        resize(length);
    };

    // 保留 SDS 给定区间内的数据, 不在区间内的数据会被覆盖或清除
    void range(int start, int end){
        if(start<0 || start>end){
            return;
        }
        int newlen=end-start+1;
        m_free=m_free + m_len - newlen;
        m_len=newlen;
        memcpy(m_str,m_str+start,newlen);
        m_str[newlen]='\0';
    };

    // 从 SDS 左右两端分别移除所有在 C 字符串中出现过的字符
    void trim(const char* cString){
        size_t left=0,right=0;
        while(right<m_len){
            bool flag=false;
            for(const char* p=cString;*p!='\0';p++){
                if(m_str[right]==*p){
                    flag=true;
                    break;
                
                }
            }
            if(!flag){
                m_str[left]=m_str[right];
                left++;
            }
            right++;
        }
        m_free+=m_len-left;
        m_len=left;
        m_str[m_len]='\0';
    
    };

    // 对比两个 SDS 字符串是否相同
    bool compare(const SDS& sds) const{
        if(m_len!=sds.m_len){
            return false;
        }else
            return memcmp(m_str,sds.m_str,m_len)==0;
    };
};


#endif /* SDS_H */

数据结构

size_t m_len  记录当前字符本身的长度

size_t m_free 记录当前字符空余的长度

cahr *m_str  记录当前所有字符,为了便于测试,我就把他放到public里了

内存分配策略

增加长度

如果新加的长度够放到缓冲区里----就放到缓冲区,改变m_len  m_free的值

如果不够,计算新的字符长度,m_len + strlen(char * str) ;

        当新的长度小于1MB,分配一个new_len*2+1长度的数组

        当新的长度大于1MB,分配一个new_len+1MB+1byte长度的数组

减少长度

留这里,不改变。

这样就很好了

  1. 避免了频繁的申请内存
  2. 0不再是结束标志位,m_len才是,可以储存任意的字符。
  3. 可以调用memcpy  memcmp来对字符进行赋值和比较

方法函数的设计

realloc

传入一个新的参数,表示新的字符串大小,用于执行内存分配的策略

    void realloc(size_t newlen){
        // 表示现在我需要newlen长度的空间了,你能不能行
        if(newlen<m_free+m_len){
            return ; //可以
        }
        //不可以,重新分配空间
        char *newstr;
        if(newlen>SDS_MAX_PREALLOC){
            newstr=new char[newlen+SDS_MAX_PREALLOC+1];
            m_free=newlen+SDS_MAX_PREALLOC - m_len;
        }else{
            newstr=new char[(newlen<<1) + 1];
            m_free=newlen*2 - m_len;
        }
        memcpy(newstr,m_str,m_len);
        delete[] m_str;
        m_str=newstr;
        
    }; 

concatenate

拼接字符串,将cString的长度为len,拼接到当前字符的末尾

    void concatenate(const char* cString, size_t len){
        realloc(m_len+len);
        memcpy(m_str+m_len,cString,len);
        m_len+=len;
        m_str[m_len]='\0';
        m_free=m_free-len;
    }

resize

改变当前字符缓冲区的大小,这里加了一个判断,看是不是小于当前已经存储的字符大小

    void resize(size_t newlen){
        if(newlen<m_len){
            newlen=m_len;
        }
        newlen++;
        char *newstr=new char[newlen+1];
        memcpy(newstr,m_str,newlen);
        newstr[newlen]='\0';
        delete[] m_str;
        m_str=newstr;
        m_free=newlen-m_len;

    }

测试代码

使用gtest

#include <gtest/gtest.h>
#include "SDS.h"
TEST(SDSTest, CompareStrings) {
    // Happy path
    {
        SDS sds1("hello");
        SDS sds2("hello");
        EXPECT_TRUE(sds1.compare(sds2));
    }

    // Edge case: empty strings
    {
        SDS sds1("");
        SDS sds2("");
        EXPECT_TRUE(sds1.compare(sds2));
    }

    // Edge case: strings of different lengths
    {
        SDS sds1("hello");
        SDS sds2("world");
        EXPECT_FALSE(sds1.compare(sds2));
    }

    // Edge case: strings with different characters
    {
        SDS sds1("hello");
        SDS sds2("hallo");
        EXPECT_FALSE(sds1.compare(sds2));
    }
}

TEST(SDSTest, CreateTest) {
    SDS s1 = SDS::create("Hello");
    ASSERT_EQ(s1.length(), 5);
    ASSERT_EQ(s1.availableSpace(), 5);
}


// Test createEmpty
TEST(SDSTest, CreateEmptyTest) {
    SDS s2 = SDS::createEmpty();
    ASSERT_EQ(s2.length(), 0);
    ASSERT_EQ(s2.availableSpace(), 0);
}

// Test concatenate
TEST(SDSTest, ConcatenateTest) {
    SDS s1 = SDS::create("Hello");
    s1.concatenate(" World");
    ASSERT_EQ(s1.length(), 11);
    ASSERT_STREQ(s1.duplicate().m_str, "Hello World");
}

// Test copy
TEST(SDSTest, CopyTest) {
    SDS s2 = SDS::createEmpty();
    s2.copy("Test");
    ASSERT_EQ(s2.length(), 4);
    ASSERT_STREQ(s2.duplicate().m_str, "Test");
}

// Test growZero
TEST(SDSTest, GrowZeroTest) {
    SDS s2 = SDS::createEmpty();
    s2.growZero(10);
    ASSERT_EQ(s2.length() + s2.availableSpace(), 11);
    ASSERT_EQ(s2.length(), 0);  // Double check the expected behavior
}

// Test compare
TEST(SDSTest, CompareTest) {
    SDS s1 = SDS::create("Hello");
    ASSERT_TRUE(s1.compare(s1.duplicate()));
    SDS s2 = SDS::createEmpty();
    ASSERT_FALSE(s1.compare(s2));
}

// Test trim
TEST(SDSTest, TrimTest) {
    SDS s1 = SDS::create("Hello World");
    s1.trim("erld");
    ASSERT_EQ(s1.length(), 5);
    ASSERT_STREQ(s1.m_str, "Ho Wo");
}

// Test range
TEST(SDSTest, RangeTest) {
    SDS s2 = SDS::create("Test");
    s2.range(1, 3);
    ASSERT_EQ(s2.length(), 3);
    ASSERT_STREQ(s2.m_str, "est");
}

内存泄露检验

valgrind --leak-check=full -s ./tinyredis_test_sds_test  

但是我不知道为啥会invalid read

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值