redis之sds(simple dynamic string)阅读笔记8-sds之字符串常用函数2
**********************************************************************
函数sdssplitlen 用分隔符将字符串分割为为多个字符串
/* Split 's' with separator in 'sep'. An array
 * of sds strings is returned. *count will be set
 * by reference to the number of tokens returned.
用sep中的分隔符分割字符串s,一个sds字符数组被返回,传入参数count会被指向数组元素的个数
 * On out of memory, zero length string, zero length
 * separator, NULL is returned.
如果出现OOM,0长度的字符串,0长度的分隔符,将返回空
 * Note that 'sep' is able to split a string using
 * a multi-character separator. For example
 * sdssplit("foo_-_bar","_-_"); will return two
 * elements "foo" and "bar".
注意,分隔符sep可以使用多字节的分隔符分割字符串。举例如下
sdssplit("foo_-_bar","_-_") 会返回两个元素"foo"和"bar"
 * This version of the function is binary-safe but
 * requires length arguments. sdssplit() is just the
 * same function but for zero-terminated strings.
 */
这个版本是二进制安全的,但是需要长度信息。sdssplit是同样的功能的函数,只不过字符串以/0结尾

sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count) {
    int elements = 0, slots = 5;
    long start = 0, j;
    sds *tokens;

    if (seplen < 1 || len < 0) return NULL; 
    //分隔符长度为0 或者 字符串长度小于0 直接返回空,这里和上面的函数说明不一致,怪哉怪哉

    tokens = s_malloc(sizeof(sds)*slots);//初始化一个数组,分配了5个字符类型的指针空间,可以指向5个字符串
    if (tokens == NULL) return NULL;

    if (len == 0) { //字符串长度为0进行了单独的操作,返回了一个空的数组
        *count = 0;
        return tokens;
    }
    for (j = 0; j < (len-(seplen-1)); j++) {
遍历字符串,查找分割符号,只需遍历到最后只有分隔符长度的位置即可终止,因为此时,不会再有新的分割字符串
举例说明:如下字符串和对应字符位置 
abhijfghij
0123456789
假设分隔符为"hij",那么我们只需查找到10-(3-1)=8为止,即到字符h即可,
因为这个时候只剩下两个字符(ij),小于分割符长度3,所以不会出现新串
        /* make sure there is room for the next element and the final one */
        确认至少还有两个元素的空间(下一个元素和最后一个元素)
        if (slots < elements+2) { // 空间不够的情况,需要重新分配,采用一贯作风的乘以2扩展方式
            sds *newtokens;
            slots *= 2;
            newtokens = s_realloc(tokens,sizeof(sds)*slots);//乘以2扩展方式
            if (newtokens == NULL) goto cleanup; //扩展内存空间失败,清理已分配内存
            tokens = newtokens;
        }
        /* search the separator */
        if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {
如果分割符是一位,那么不用调用memcmp函数比较,这个是为了提高效率
            tokens[elements] = sdsnewlen(s+start,j-start);//通过原串创建创建新串,截取其中一段
            if (tokens[elements] == NULL) goto cleanup;
            elements++; //数组元素++
            start = j+seplen; //起始位置指向当前分隔符下一个位置,即跳过分隔符
            j = j+seplen-1; /* skip the separator */  //遍历位置即跳过分隔符
        }
    }
    /* Add the final element. We are sure there is room in the tokens array. */
    //因为前面保证了留有最后一个的空间,所以这里无需分配空间
    tokens[elements] = sdsnewlen(s+start,len-start);//剩下所有字符为一个串
    if (tokens[elements] == NULL) goto cleanup; //创建失败,就全部清除
    elements++;
    *count = elements;
    return tokens;

cleanup:
    {
        int i;
        for (i = 0; i < elements; i++) sdsfree(tokens[i]); // 释放字符串数组里面的每个元素(这里的每个元素也是一个字符串)
        s_free(tokens);// 释放字符串数组本身,这个也是一个字符串指针数组
        *count = 0; //个数为0
        return NULL;
    }
}

这个函数需要注意 :没有去除分割出来的空字符串,就拿我们上面的例子来说“abhijfghij”用"hij"分割来说
最后得到的字符串是3个,分为别"ab","fg","",最后一个是空串
**********************************************************************
函数sdsfreesplitres释放从sdssplitlen获取的字符串数组,作用等同于sdssplitlen中的cleanup段
/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */
void sdsfreesplitres(sds *tokens, int count) {
    if (!tokens) return; //为空则无需释放
    while(count--) //从后往前遍历释放每个字符串空间
        sdsfree(tokens[count]);
    s_free(tokens); // 释放数组本身空间
}
**********************************************************************
函数sdscatrepr 用来格式化字符串
/* Append to the sds string "s" an escaped string representation where
 * all the non-printable characters (tested with isprint()) are turned into
 * escapes in the form "\n\r\a...." or "\x<hex-number>".
在sds后面添加一个转义字符串,所有不可打印字符(用函数isprint测试)被转化成转义格式
 * After the call, the modified sds string is no longer valid and all the
 * references must be substituted with the new pointer returned by the call. */
sds sdscatrepr(sds s, const char *p, size_t len) {
    s = sdscatlen(s,"\"",1);
    while(len--) {
        switch(*p) {
        case '\\':
        case '"':
            s = sdscatprintf(s,"\\%c",*p);
            break;
        case '\n': s = sdscatlen(s,"\\n",2); break;
        case '\r': s = sdscatlen(s,"\\r",2); break;
        case '\t': s = sdscatlen(s,"\\t",2); break;
        case '\a': s = sdscatlen(s,"\\a",2); break;
        case '\b': s = sdscatlen(s,"\\b",2); break;
        default:
            if (isprint(*p))
                s = sdscatprintf(s,"%c",*p);
            else
                s = sdscatprintf(s,"\\x%02x",(unsigned char)*p);
            break;
        }
        p++;
    }
    return sdscatlen(s,"\"",1);
}
注意这个函数在传输和通信的时候极为有用,可以将不可见字符转化为可见模式进行传输
**********************************************************************
函数 is_hex_digit  判断一个字符是否是16进制有效的
/* Helper function for sdssplitargs() that returns non zero if 'c'
 * is a valid hex digit. */
int is_hex_digit(char c) {
    return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
           (c >= 'A' && c <= 'F');
}
**********************************************************************
函数hex_digit_to_int 将16单个字符从16进制转化为10进制
/* Helper function for sdssplitargs() that converts a hex digit into an
 * integer from 0 to 15 */
int hex_digit_to_int(char c) {
    switch(c) {
    case '0': return 0;
    case '1': return 1;
    case '2': return 2;
    case '3': return 3;
    case '4': return 4;
    case '5': return 5;
    case '6': return 6;
    case '7': return 7;
    case '8': return 8;
    case '9': return 9;
    case 'a': case 'A': return 10;
    case 'b': case 'B': return 11;
    case 'c': case 'C': return 12;
    case 'd': case 'D': return 13;
    case 'e': case 'E': return 14;
    case 'f': case 'F': return 15;
    default: return 0;
    }
}
**********************************************************************
函数sdssplitargs 用来处理输入参数
/* Split a line into arguments, where every argument can be in the
 * following programming-language REPL-alike form:
分割一行字符串变为参数,每个参数可能像如下程序中交互式格式(PERL : read eval print loop)
 * foo bar "newline are supported\n" and "\xff\x00otherstuff"
 *
 * The number of arguments is stored into *argc, and an array
 * of sds is returned.
参数的个数被存储在变量argc中,一个sds字符串数组被返回
 * The caller should free the resulting array of sds strings with
 * sdsfreesplitres().
(再使用完毕后)调用者需要使用函数sdsfreesplitres释放返回的sds字符串数组
 * Note that sdscatrepr() is able to convert back a string into
 * a quoted string in the same format sdssplitargs() is able to parse.
注意,函数sdscatrepr可以将一个字符串转化成函数sdssplitargs可以处理的格式
 * The function returns the allocated tokens on success, even when the
 * input string is empty, or NULL if the input contains unbalanced
 * quotes or closed quotes followed by non space characters
 * as in: "foo"bar or "foo'
 */
当调用成功的时候这个函数返回分配好的关键字数组,即使输入字符串是空的,
或者返回NULL,如果输入的行包括不对称的引号或者最后一个封闭引号后面还存在非控字符

sds *sdssplitargs(const char *line, int *argc) {
    const char *p = line; //指向输入行
    char *current = NULL; //指向当前截取的参数字符串
    char **vector = NULL;  //所有截取的参数字符串

    *argc = 0; //初始个数为0
    while(1) {//永真循环,需要内部中断跳出
        /* skip blanks */
        while(*p && isspace(*p)) p++; //过滤所有空字符
        if (*p) { //遇到的非空字符 并且不是结尾
            /* get a token */
            int inq=0;  /* set to 1 if we are in "quotes" */  
            //进入双引号中的标志
            int insq=0; /* set to 1 if we are in 'single quotes' */
            //进入单引号中的标志
            int done=0;
            //一个参数字符串完成的标志

            if (current == NULL) current = sdsempty();  
            //如果当前指向的字符串为空,那么需要分配一个新的空字符串给current
            while(!done) { //如果一个参数字符串还没有完成,就一直循环
                if (inq) { //进入了双引号区域
                //如果是\\xa5 类似的格式,那么必定是不可见字符的ascii值
                    if (*p == '\\' && *(p+1) == 'x' &&  is_hex_digit(*(p+2)) &&  is_hex_digit(*(p+3)))
                    {
                        unsigned char byte;
                        byte = (hex_digit_to_int(*(p+2))*16)+ hex_digit_to_int(*(p+3)); //获取ascii值
                        current = sdscatlen(current,(char*)&byte,1); //转化成字符 拼接到 当前参数字符串
                        p += 3; //跳过3个字符
                    } else if (*p == '\\' && *(p+1)) {  //如果是转义符的其它情况,那么再向后获一个
                        char c;
                        p++;//跳过1个字符
                        switch(*p) { //对应各种转换类型,没有转化类型的就如普通字符一样
                        case 'n': c = '\n'; break;
                        case 'r': c = '\r'; break;
                        case 't': c = '\t'; break;
                        case 'b': c = '\b'; break;
                        case 'a': c = '\a'; break;
                        default: c = *p; break;
                        }
                        current = sdscatlen(current,&c,1); //将这个转义字符拼接到当前参数字符串中
                    } else if (*p == '"') {
                        /* closing quote must be followed by a space or
                         * nothing at all. */
                         关闭的引号后面必须为空字符或者没有任何东西(即已经结束)
                        if (*(p+1) && !isspace(*(p+1))) goto err;
                        //*(p+1)表示还有内容  !isspace(*(p+1))表示还有非空内容  ,那么这个参数就是有问题的,错误处理 
                        done=1; //如果这个参数是正常的,那么到了关闭引号这里就是结束了,意味着一个参数字符串完成了
                    } else if (!*p) {
                        /* unterminated quotes */ 无结尾引号
                        !*p 表示已经结尾了,但是还没有找到结尾引号,所以需要错误处理
                        goto err;
                    } else { //其他情况,正常拼接一个字符
                        current = sdscatlen(current,p,1);
                    }
                } else if (insq) {
                    if (*p == '\\' && *(p+1) == '\'') { //如果是对单引号的转义
                        p++;
                        current = sdscatlen(current,"'",1);// 拼接单引号
                    } else if (*p == '\'') { //这里同双引号部分的逻辑
                        /* closing quote must be followed by a space or
                         * nothing at all. */
                        if (*(p+1) && !isspace(*(p+1))) goto err;
                        done=1;
                    } else if (!*p) {
                        /* unterminated quotes */
                        goto err;
                    } else {
                        current = sdscatlen(current,p,1);
                    }
                } else {
                    switch(*p) { //其他各种情况 
                    case ' ':
                    case '\n':
                    case '\r':
                    case '\t':
                    case '\0':
                        done=1; //遇到空字符串,表示一个参数已经完成
                        break;
                    case '"':
                        inq=1;  //遇到开始的双引号,表示进入双引号区域(一个参数的区域)
                        break;
                    case '\'': //遇到开始的双引号,也表示进入双引号区域(一个参数的区域)
                        insq=1;
                        break;
                    default:  //正常字符情况,拼接一个字符即可
                        current = sdscatlen(current,p,1);
                        break;
                    }
                }
                if (*p) p++; //如果还没有到参数行末尾, 继续下一个字符
            }
            /* add the token to the vector */
            vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); //分配指向参数字符串的字符串指针数组
            vector[*argc] = current; //将当前获取的参数字符串存储到相应位置
            (*argc)++; //返回参数的个数+1
            current = NULL; //一个已经完成,将当前指针清空,获取下一个参数字符串
        } else {
            /* Even on empty input string return something not NULL. */ 
            即使是空的输入字符串 也返回非NULL值
            if (vector == NULL) vector = s_malloc(sizeof(void*));
            return vector; //已经到结尾,返回所有的参数字符串
        }
    }

err:
    while((*argc)--)
        sdsfree(vector[*argc]); //挨个释放内存空间
    s_free(vector); //释放指针数组的内存空间
    if (current) sdsfree(current); //释放当前参数字符串的内存空间
    *argc = 0; //参数各位设置为0
    return NULL;
}
**********************************************************************
函数sdsmapchars 替换字符串
/* Modify the string substituting all the occurrences of the set of
 * characters specified in the 'from' string to the corresponding character
 * in the 'to' array.
修改字符串所有位置上的字符,从'from'串中的字符对应替换为'to'串中的对应字符
 * For instance: sdsmapchars(mystring, "ho", "01", 2)
 * will have the effect of turning the string "hello" into "0ell1".
 *
 * The function returns the sds string pointer, that is always the same
 * as the input pointer since no resize is needed. */
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) {
    size_t j, i, l = sdslen(s);

    for (j = 0; j < l; j++) { //遍历整个字符串
        for (i = 0; i < setlen; i++) { //遍历替换字符
            if (s[j] == from[i]) { //如果存在需要替换的字符
                s[j] = to[i];  //进行替换
                break;
            }
        }
    }
    return s;
}
**********************************************************************
函数sdsjoin  连接一个c类型的字符串数组到sds字符串,中间用特定的分隔符分割
/* Join an array of C strings using the specified separator (also a C string).
 * Returns the result as an sds string. */
sds sdsjoin(char **argv, int argc, char *sep) {
    sds join = sdsempty();  //sds空字符串
    int j;

    for (j = 0; j < argc; j++) { //挨个连接
        join = sdscat(join, argv[j]);
        if (j != argc-1) join = sdscat(join,sep); //如果不是最后一个,那么需要添加分隔符
    }
    return join;
}
**********************************************************************
函数sdsjoinsds  连接一个sds类型的字符串数组到sds字符串类型的字符串数组到sds字符串,中间用特定的分隔符分割
/* Like sdsjoin, but joins an array of SDS strings. */
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) {
    sds join = sdsempty();
    int j;

    for (j = 0; j < argc; j++) {
        join = sdscatsds(join, argv[j]);
        if (j != argc-1) join = sdscatlen(join,sep,seplen);
    }
    return join;
}