从源码中分析SDS相较于C字符串的优势

这篇具有很好参考价值的文章主要介绍了从源码中分析SDS相较于C字符串的优势。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

从本篇文章开始会持续更新有关"redis数据结构源码"的分析。[分析的源码是redis7.2.2版本的,有时候会结合之前的版本]。由于能力有限,有些地方可能有些错误,还望指正。
在分析"intset"的源码的发现。在7.2.2版本,数据类型"set"的底层编码多了一种"listpack",但是之前的版本并没有。本着严谨的态度重新查看了源码以及配置文件,并作出相应的修改。主要修改的地方就是各个版本的数据类型和底层编码的对应图以及编码之间的阈值。

Type && Encoding

redis的每种数据结构都被封装在"redisObject"中,先来看一下"redisObject"长什么样子。

struct redisObject {
    unsigned type:4;//表示数据结构的类型,占4bits
    unsigned encoding:4;//数据结构底层使用的编码,占4bits
    unsigned lru:LRU_BITS; //用于LRU或者LFU内存淘汰算法,占24bits
    int refcount;//引用计数,占4bytes,引用计数变为0对象被销毁,内存被回收
    void *ptr;//指向具体的数据,占8bytes
};

接下来看一下"type"都有哪些[以下是五种基本的数据结构]

#define OBJ_STRING 0    /* String object. */
#define OBJ_LIST 1      /* List object. */
#define OBJ_SET 2       /* Set object. */
#define OBJ_ZSET 3      /* Sorted set object. */
#define OBJ_HASH 4      /* Hash object. */

接下来看一下数据结构对应的"encoding"
从源码中分析SDS相较于C字符串的优势,Redis,sds,redis,源码,C字符串,encoding

redis在3.2版本时引入了"quicklist"[也就是linkedlist+ziplist]
从源码中分析SDS相较于C字符串的优势,Redis,sds,redis,源码,C字符串,encoding

从源码中分析SDS相较于C字符串的优势,Redis,sds,redis,源码,C字符串,encoding
redis在5.0版本引入"listpack"

  • 虽然5.0版本引入了"listpack"解决了"ziplist"级联更新的问题,但是hash和zset结构使用的仍然是ziplist。listpack目前只使用在新增加的Stream数据结构中。

从源码中分析SDS相较于C字符串的优势,Redis,sds,redis,源码,C字符串,encoding

redis在7版本将"ziplist"替换为"listpack"
从源码中分析SDS相较于C字符串的优势,Redis,sds,redis,源码,C字符串,encoding

从源码中分析SDS相较于C字符串的优势,Redis,sds,redis,源码,C字符串,encoding

接下来分析"sds"的源码

sds

首先看一下sds的结构
redis-7.2.2\src\sds.h

typedef char * sds;//sds的本质就是char*指向一块连续的内存

sds分为“sdshdr5”、“sdshdr8”、“sdshdr16”、“sdshdr32”、“sdshdr64”五种结构。其中"sdshdr5"从未使用过。

/* Note: sdshdr5 is never used, we just access the flags byte directly.However is here to document the layout of type 5 SDS strings. */
//sdshdr5从未被使用过,只是用它来直接访问flags字节
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; //1byte
    uint8_t alloc;//1byte,不包括头部和终止符
    unsigned char flags; /* 3 lsb of type, 5 unused bits  1byte*/
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used  2byte*/
    uint16_t alloc; /* excluding the header and null terminator  2byte*/
    unsigned char flags; /* 3 lsb of type, 5 unused bits  1byte*/
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used  4byte*/
    uint32_t alloc; /* excluding the header and null terminator  4byte*/
    unsigned char flags; /* 3 lsb of type, 5 unused bits  1byte*/
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used  8byte*/
    uint64_t alloc; /* excluding the header and null terminator  8byte*/
    unsigned char flags; /* 3 lsb of type, 5 unused bits  1byte*/
    char buf[];
};

以上几种结构的布局很相似都是包含"len\alloc\flags\buf"不同的是记录“len\alloc”的字节数不相同。
其中flags的类型如下

#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4

encoding

sds底层有三种编码方式
redis-7.2.2\src\server.h

#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */

接下来依次看一下编码方式的选择

createStringObject

[redis-7.2.2\src\object.c]

#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}

从以上代码中可以看出,会根据字符串的长度选择合适的编码方式[embstr or raw]。长度的阈值为44,为什么是44呢,稍后再做解释。接下来看一下"createEmbeddedStringObject"和"createRawStringObject"

createEmbeddedStringObject

/* Create a string object with encoding OBJ_ENCODING_EMBSTR, that is
 * an object where the sds string is actually an unmodifiable string
 * allocated in the same chunk as the object itself. */
 /*
创建一个底层编码为"OBJ_ENCODING_EMBSTR"的string对象
实际上,"embstr"编码的字符串是不能修改的字符串
和"redisobject"分配在同一个内存块中
 */
robj *createEmbeddedStringObject(const char *ptr, size_t len) {
	//将redisObject和sdshdr8分配在同一个chunk中
    robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);//这里加1指的是结尾的'\0'字符
    struct sdshdr8 *sh = (void*)(o+1);

    o->type = OBJ_STRING;
    o->encoding = OBJ_ENCODING_EMBSTR;
    o->ptr = sh+1;
    o->refcount = 1;
    o->lru = 0;

    sh->len = len;
    sh->alloc = len;//新创建的字符串的cap和len保持一致
    sh->flags = SDS_TYPE_8;
    //const char *SDS_NOINIT = "SDS_NOINIT";
    //将传入参数ptr指向内存的数据存储到buf中
    if (ptr == SDS_NOINIT)
        sh->buf[len] = '\0';
    else if (ptr) {
        memcpy(sh->buf,ptr,len);
        sh->buf[len] = '\0';
    } else {
        memset(sh->buf,0,len+1);
    }
    return o;
}

总结
  • 编码为embstr的字符串是无法被修改的字符串
  • 对于编码为embstr的字符串使用的sds结构为"sdshdr8"
  • 新创建的字符串的cap和len保持一致,并且字符的结尾含有’\0’结束符[猜想这里应该是为了兼容c中字符数组并且必要时候能够使用c的库函数]
  • 编码为embstr的字符串,redisObject和sds分配在同一个chunk中,如下图所示

从源码中分析SDS相较于C字符串的优势,Redis,sds,redis,源码,C字符串,encoding

createRawStringObject

/* Create a string object with encoding OBJ_ENCODING_RAW, that is a plain
 * string object where o->ptr points to a proper sds string. */
//创建一个底层编码为"raw"的字符串,可选择的sds的结构为
//[sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64]
robj *createRawStringObject(const char *ptr, size_t len) {
    return createObject(OBJ_STRING, sdsnewlen(ptr,len));
    //createObject根据传入的参数"type、sds"创建新的"redisObject"
    //sdsnewlen根据传入的参数"char*、size_t"创建新的sds
}
sds sdsnewlen(const void *init, size_t initlen) {
    return _sdsnewlen(init, initlen, 0);
}

/*
根据"init指向的content"和initlen创建一个新的sds
如果init指向的内容是NULL,则将字符串初始化为zero bytes
如果init是SDS_NOINIT,buf将不会被初始化
*/
/*
The string is always null-terminated (all the sds strings are, always) 
so even if you create an sds string with:
mystring = sdsnewlen("abc",3);
You can print the string with printf() 
as there is an implicit \0 at the end of the string.
However the string is binary safe and can contain\0 
characters in the middle, 
as the length is stored in the sds header.

将上述一段英文翻译成中文:
所有的sds string都是以空字符即'\0'结尾;
用如下的方式创建一个新的sds string:
mystring = sdsnewlen("abc",3);
可以使用"printf()"函数打印mystring,因为mystring的结尾有个隐式的'\0'字符。
但是mystring是二进制安全的并且可以在在字符串的中间包含'\0'的字符,
因为字符串的长度会被记录在sds的header中。
*/
sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {
    void *sh;
    sds s;
    //根据initlen大小获取sds的结构类型
    char type = sdsReqType(initlen);
    /* Empty strings are usually created in order to append. Use type 8
     * since type 5 is not good at this. */
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    //获取头部大小
    int hdrlen = sdsHdrSize(type);
    unsigned char *fp; /* flags pointer. */
    size_t usable;

    assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */
    /*
    	Allocate memory or panic.
    	usable is set to the usable size if non NULL. 
    	如果能够成功分配内存,那么usable=hdrlen+initlen+1
    */
    sh = trymalloc?
        s_trymalloc_usable(hdrlen+initlen+1, &usable) :
        s_malloc_usable(hdrlen+initlen+1, &usable);
    if (sh == NULL) return NULL;
    if (init==SDS_NOINIT)
        init = NULL;
    else if (!init)
        memset(sh, 0, hdrlen+initlen+1);
    s = (char*)sh+hdrlen;
    fp = ((unsigned char*)s)-1;
    //正常情况下,usable和initlen保持一致
    usable = usable-hdrlen-1;
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
    switch(type) {
        case SDS_TYPE_5: {
            *fp = type | (initlen << SDS_TYPE_BITS);
            break;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
    }
    //将init指向的内容复制到buf中
    if (initlen && init)
        memcpy(s, init, initlen);
    s[initlen] = '\0';
    return s;
}
robj *createObject(int type, void *ptr) {
//ptr指向创建好的sds
//为redisObject分配一块新的内存空间,由此也可以得出底层编码为"raw"的redisObject和sds分开存储属于两块不同的连续内存块
    robj *o = zmalloc(sizeof(*o));
    o->type = type;
    o->encoding = OBJ_ENCODING_RAW;
    o->ptr = ptr;
    o->refcount = 1;
    o->lru = 0;
    return o;
}
总结
  • 底层编码为"raw"的string是可以修改的,编码为"embstr"的string若想要修改需要先将其的编码变为"raw"才可以被修改。如下图所示
    从源码中分析SDS相较于C字符串的优势,Redis,sds,redis,源码,C字符串,encoding

  • 根据传入的字符串的initlen从[sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64]选择合适的sds头部结构

  • 所有的字符串都以’\0’作为结束符,可以使用printf()函数进行打印;二进制安全的,字符串中间可以存储’\0’字符,长度信息存储在sds_header中

  • 编码为"raw"的字符串,redisObject和sds存储在不同的内存块中,如下图所示
    从源码中分析SDS相较于C字符串的优势,Redis,sds,redis,源码,C字符串,encoding

createStringObjectFromLongDouble

#define MAX_LONG_DOUBLE_CHARS 5*1024
 /*
 从长双精度类型创建字符串对象。如果humanfriendly为非零,则不使用指数
 格式并在末尾修剪尾随的零,但是这会导致精度的损失。否则使用exp格式,并
 且不修改snprintf()的输出。“humanfriendly”选项用于INCRBYFLOAT和
 HINCRBYFLOAT。
 */
robj *createStringObjectFromLongDouble(long double value, int humanfriendly) {
    char buf[MAX_LONG_DOUBLE_CHARS];
    int len = ld2string(buf,sizeof(buf),value,humanfriendly? LD_STR_HUMAN: LD_STR_AUTO);
    return createStringObject(buf,len);
}

从源码中分析SDS相较于C字符串的优势,Redis,sds,redis,源码,C字符串,encoding

总结

从以上代码可以看出“long double”类型的浮点数以“string”的形式存储,底层的编码方式为"embstr"或者“raw”

createStringObjectFromLongLongWithOptions

/* Create a string object from a long long value according to the specified flag. */
#define LL2STROBJ_AUTO 0       /* automatically create the optimal string object */
#define LL2STROBJ_NO_SHARED 1  /* disallow shared objects */
#define LL2STROBJ_NO_INT_ENC 2 /* disallow integer encoded objects. */
robj *createStringObjectFromLongLongWithOptions(long long value, int flag) {
    robj *o;
    //#define OBJ_SHARED_INTEGERS 10000
    if (value >= 0 && value < OBJ_SHARED_INTEGERS && flag == LL2STROBJ_AUTO) {
    //value值处于[0,10000)进行共享
        o = shared.integers[value];
    } else {
    //在long的范围内,用int进行编码
        if ((value >= LONG_MIN && value <= LONG_MAX) && flag != LL2STROBJ_NO_INT_ENC) {
            o = createObject(OBJ_STRING, NULL);
            o->encoding = OBJ_ENCODING_INT;
            //value值内嵌在在ptr中
            o->ptr = (void*)((long)value);
        } else {
        //超出long表示的范围则根据长度在"embstr or raw"中选择编码
            char buf[LONG_STR_SIZE];
            int len = ll2string(buf, sizeof(buf), value);
            o = createStringObject(buf, len);
        }
    }
    return o;
}

总结

  • value值在[0,10000)内共享redisobject
    从源码中分析SDS相较于C字符串的优势,Redis,sds,redis,源码,C字符串,encoding

  • value在long范围内,底层编码为int。并且value值内嵌在ptr中
    从源码中分析SDS相较于C字符串的优势,Redis,sds,redis,源码,C字符串,encoding

  • value不在long范围内,根据len的长度选择合适的编码,“embstr"或者"raw”

相关操作

redis-7.2.2\src\sds.c
通过以上源码的分析,我们发现不论字符串以何种编码方式创建。sds的cap都和len保持一致,这是为了节约内存,因为绝大多数情况下都不会使用"append"操作。
接下来就看一下"append"操作的流程,

sdscatlen


/*
将t指向的字符串拼接到sds指向的字符串的末尾,
拼接之后,传入的sds不再有效,需要使用返回的新sds代替旧的传入的sds
*/
sds sdscatlen(sds s, const void *t, size_t len) {
	//当前sds指向字符串的长度
    size_t curlen = sdslen(s);
    //通过sdsMakeRoomFor计算拼接之后新的字符串的长度并返回新的sds
    s = sdsMakeRoomFor(s,len);
    //没有足够的空间,拼接失败
    if (s == NULL) return NULL;
    //将t指向的字符串拼接到s的末尾,s+curlen存储的是'\0'结尾符,也即是t指向的字符串会覆盖sds指向字符串的'\0'结尾符
    memcpy(s+curlen, t, len);
    //设置新sds的长度
    sdssetlen(s, curlen+len);
    //加上'\0'结尾符
    s[curlen+len] = '\0';
    return s;
}

接下来看一下如何"扩容"的

/*
为sds分配更多的空间避免重复的进行append操作
*/
sds sdsMakeRoomFor(sds s, size_t addlen) {
    return _sdsMakeRoomFor(s, addlen, 1);
}
/* Enlarge the free space at the end of the sds string so that the caller
 * is sure that after calling this function can overwrite up to addlen
 * bytes after the end of the string, plus one more byte for null term.
 * If there's already sufficient free space, this function returns without any
 * action, if there isn't sufficient free space, it'll allocate what's missing,
 * and possibly more:
 * When greedy is 1, enlarge more than needed, to avoid need for future reallocs
 * on incremental growth.
 * When greedy is 0, enlarge just enough so that there's free space for 'addlen'.
 *
 * Note: this does not change the *length* of the sds string as returned
 * by sdslen(), but only the free buffer space we have. */
/*
扩大sds字符串末尾的可用空间,以便调用者在调用此函数后可以覆盖原字符串的addlen字节和'\0'字符
如果已经有足够的空闲空间,这个函数返回时不做任何操作,
如果没有足够的空闲空间,它将分配缺失的部分,甚至更多:
当greedy为1时,分配比需要的更多,以避免再次扩容时需要重新分配。
当greedy为0时,将其放大到足够大以便为addlen腾出空间即可。
注意:这不会改变sdslen()返回的sds字符串的长度,而只会改变我们拥有的空闲缓冲区空间也即是alloc。
*/
sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) {
    void *sh, *newsh;
    //获取旧sds的可用space,[s->alloc - s->len];
    size_t avail = sdsavail(s);
    size_t len, newlen, reqlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;
    size_t usable;

    //可用空闲空间满足要求直接返回,什么也不做
    if (avail >= addlen) return s;
    
    
	//获取旧sds字符串的长度len
    len = sdslen(s);
    
    sh = (char*)s-sdsHdrSize(oldtype);
    //拼接之后新字符串的长度
    reqlen = newlen = (len+addlen);
    
    assert(newlen > len);   /* Catch size_t overflow */
    
    //greedy=1表示需要预先多分配一些内存
    if (greedy == 1) {
    //#define SDS_MAX_PREALLOC (1024*1024) [1MB]
    //newlen小于1MB,翻倍扩容
        if (newlen < SDS_MAX_PREALLOC)
            newlen *= 2;
        else
        //需要的长度大于等于1MB,多扩容1MB
            newlen += SDS_MAX_PREALLOC;
    }
	//根据newlen计算需要使用的sds类型
    type = sdsReqType(newlen);

   
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;

    hdrlen = sdsHdrSize(type);
    //多分配1byte是为了存储末尾的'\0'字符
    assert(hdrlen + newlen + 1 > reqlen);  /* Catch size_t overflow */
    //sds头部类型不变,在原地扩容内存
    if (oldtype==type) {
        newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
        if (newsh == NULL) return NULL;
        //获取新sds的buf
        s = (char*)newsh+hdrlen;
    } else {
    //sds头部类型改变,重新分配内存
        newsh = s_malloc_usable(hdrlen+newlen+1, &usable);
        if (newsh == NULL) return NULL;
        //拷贝旧sds中的内容到新的sds中
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        //设置len
        sdssetlen(s, len);
    }
    //计算空闲空间
    usable = usable-hdrlen-1;
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
        //设置alloc
    sdssetalloc(s, usable);
    return s;
}

总结

从源码中分析SDS相较于C字符串的优势,Redis,sds,redis,源码,C字符串,encoding

阈值44

接下来分析一下为什么编码"embstr"和编码"raw"的长度界限值为44
先来回顾一下"redisObject"的样子

struct redisObject {
    unsigned type:4;//表示数据结构的类型,占4bits
    unsigned encoding:4;//数据结构底层使用的编码,占4bits
    unsigned lru:LRU_BITS; //用于LRU或者LFU内存淘汰算法,占24bits
    int refcount;//引用计数,占4bytes,引用计数变为0对象被销毁,内存被回收
    void *ptr;//指向具体的数据,占8bytes
};

通过上述代码,可以计算出redisObject所占用的内存大小为:

4bits+4bits+24bits+4bytes+8bytes=16bytes

通过"createEmbeddedStringObject"的源码分析,发现"embstr"编码的字符串使用的sds结构为sdshdr8

struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; //1byte
    uint8_t alloc; //1byte
    unsigned char flags; //1byte
    char buf[];
};

通过上述代码,可以计算出"sds"中除了存储数据的buf,其余所占用的内存空间大小为:

1byte+1byte+1byte=3bytes

通过对"“和”"的源码分析发现,不论哪种编码[embstr or raw]的字符串都是以’\0’作为结束字符的,所以还需要占用额外的1byte存储结尾的标识符。

整体计算下来,一个完整的字符串除了存储的数据所占用的内存空间大小为:

16bytes+3bytes+1byte=20bytes

而,redis的内存分配器"jemalloc\tcmalloc"等分配内存大小的单位是"2\4\8\16\32\64"字节等,为了能容纳一个完整的"embstr"字符串,至少会分配32字节的内存,至多会分配64字节的内存。如果64字节不够存储的话,redis就将其视为一个大字符串,不再使用"embstr"进行编码,而是采用"raw"进行编码。

所以"embstr"和"raw"之间的阈值为44字节

64bytes-20bytes=44bytes

sds VS C字符串

  • redis规定字符串的长度为"512MB"。

*sds的本质还是char ,但是相比与"C字符串"多了一些字段"len,alloc,flag",优势如下文章来源地址https://www.toymoban.com/news/detail-794062.html

  • 以O(1)的时间复杂度快速获取字符串的长度。因为字符串的长度会记录在sds的header中。
  • 二进制安全。头部结构中记录字符串的长度使得字符串中间位置可以存储’\0’字符。从而可以存储任何类型的数据,比如文本,视频,图片,程序代码等。
  • 不同的数据对应不同的底层编码,更加灵活,更加节省内存。
  • 拼接(扩充)安全。字符串拼接之前会先加查是否有足够的空闲空间,没有的话先扩充内存然后才进行拼接操作。

到了这里,关于从源码中分析SDS相较于C字符串的优势的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis数据结构与对象-字符串对象SDS

    Redis没有使用C的字符串,而是自己构建了简单动态字符串(Simple Dynamic String),简称SDS。通过这种字符串格式能够对redis字符串操作进行提速。下面介绍原理。 sds数据格式如下: 比如,一个sds 中存的是 “Redis” ,那么buf 中是一个char型的数组,存5个字符R, e,d,i,s len =5;free

    2023年04月16日
    浏览(46)
  • 【Redis】关于Redis数据结构简单动态字符串(SDS)的一些杂记

    推荐几篇关于SDS数据结构讲解较为详细的文章: 一、简单动态字符串 — Redis 设计与实现 (redisbook.readthedocs.io) 二、深入理解Redis之简单动态字符串 - itbsl - 博客园 (cnblogs.com) 三、Redis内部数据结构详解(2)——sds - 铁蕾的个人博客 (zhangtielei.com) 四、简单动态字符串 — Redis 设计与

    2023年04月14日
    浏览(42)
  • 正则表达式 vs. 字符串处理:解析优势与劣势

    1. 正则表达式起源与演变 正则表达式(Regular Expression)最早由美国数学家斯蒂芬·科尔内基(Stephen Kleene)于1956年提出,用于描述字符串的模式匹配规则。随后在计算机领域得到广泛应用,成为文本处理和匹配的重要工具。 正则可视化 | 一个覆盖广泛主题工具的高效在线平台

    2024年03月27日
    浏览(46)
  • 原生OpenFeign相较于传统HTTP工具的优化和原理

    本篇介绍的是springcloud-openfeign的底层框架io.github.openfeign,重点不是框架如何使用,而是介绍Feign如何基于传统HTTP工具使用方式进行抽象改进,提升其灵活性,并简单介绍一下其抽象组件及主要组件的使用时机 以常用的OkHttp3工具框架为例,其一个大致的流程如下: 其流程从下

    2024年02月07日
    浏览(37)
  • Godot 4 源码分析 - 增加格式化字符串功能

    Godot 4的主要字符串类型为String,已经设计得比较完善了,但有一个问题,格式化这块没怎么考虑。 String中有一个format函数,但这个函数只有两个参数,这咋用? 查找使用例子,都是这种效果 一看就懵。哪里有之前用的带%s %d...之类的格式化用得舒服。 动手实现一个 提供s

    2024年02月14日
    浏览(44)
  • JAVASE进阶:强推!源码分析——字符串拼接底层原理、StringBuilder底层原理

    👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习 🌌上期文章:JAVASE进阶:String常量池内存原理分析、字符串输入源码分析 📚订阅专栏:JAVASE进阶 希望文章对你们有所帮助 这是比较重要的内容,学习原理很重要,啃源码也很重要!!! 字符串 常量 的

    2024年02月20日
    浏览(43)
  • Golang校验字符串是否JSON格式方法json.Valid源码解析

    上篇文章《Golang中如何校验字符串是否为JSON格式?》主要讲解了使用json.Valid校验字符串是否JSON格式的使用方法,本文来剖析一下json.Valid方法的源码。 json.Valid方法定义: scan := newScanner() 获取一个 scanner 类型的对象,关键的是checkValid方法,checkValid源码如下: 首先调用了sc

    2023年04月26日
    浏览(41)
  • 动态规划(一) 变态青蛙跳台阶、最大连续子数组和、字符串分割 附源码讲解

    实现 import java.util.*; public class Main { public static void main(String[] args) { Scanner scan = new Scanner(System.in); while (scan.hasNext()){ int length = scan.nextInt(); int[] array = new int[length]; for (int i = 0; i length; i++) { array[i] = scan.nextInt(); } System.out.println(findResult(array)); } } private static int findResult(int[] array) {

    2024年04月17日
    浏览(52)
  • Hotspot源码解析-第十九章-ClassLoaderData、符号表、字符串表的初始化

    讲解本章先从一张图开始 众所周知,Java类的相关信息都是存储在元空间中的,但是是怎么存储的,相信很多读者是不清楚的,这里就不得不涉及到ClassLoaderDataGraph、classLoader、classLoaderData(简称CLD)和Klass的概念及他们四者的关系,这里简单描述下他们的概念,具体细节放到

    2024年01月17日
    浏览(46)
  • Python数据分析实战-格式化字符串的两种方法(附源码和实现效果)

    实现功能 格式化字符串的两种方法 实现代码 实现效果 本人读研期间发表5篇SCI数据挖掘相关论文,现在某研究院从事数据挖掘相关科研工作,对数据挖掘有一定认知和理解,会结合自身科研实践经历不定期分享关于python机器学习、深度学习、数据挖掘基础知识与案例。 致力

    2024年02月13日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包