• string数据结构


    1. string概念

    Go标准库builtin给出了所有内置类型的定义。
    源代码位于src/builtin/builtin.go,其中关于string的描述如下:

    // string is the set of all strings of 8-bit bytes, conventionally but not
    // necessarily representing UTF-8-encoded text. A string may be empty, but
    // not nil. Values of string type are immutable.
    type string string
    
    

    string是8比特字节的集合,通常是但并不一定非得是UTF-8编码的文本。

    另外提到的两点:

    1. string可以为空,长度为0,但是不是nil
    2. string对象不可以修改

    2.string数据结构

    string在源码中定义的结构体如下

    type stringStruct struct {
        str unsafe.Pointer
        len int
    }
    
    • stringStruct.str:字符串的首地址;
    • stringStruct.len:字符串的长度;

    string数据结构跟切片有些类似,只不过切片还有一个表示容量的成员,事实上string和切片,准确的说是byte切片经常发生转换。

    3.string操作

    3.1 声明

    声明一个string变量并赋值

    var str string
    str = "hello world!"
    

    字符串构建过程是先构建stringStruct,再转换成string, 转换过程中的源码如下:

    func gostringnocopy(str *byte) string {                              // 根据字符串地址构建string
        ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)} // 先构造stringStruct
        s := *(*string)(unsafe.Pointer(&ss))                             // 再将stringStruct转换成string
        return s
    }
    

    string 在runtime包中是stringStruct,对外呈现叫做string.

    3.2 字节切片转string

    byte切片可以很方便的转换成string,使用内置string()方法

    func GetStringBySlice(s []byte) string {
        return string(s)
    }
    

    需要注意的是这种转换需要一次内存拷贝

    转换过程如下:

    1. 根据切片的长度申请内存空间,假设内存地址为p,切片长度为len(b);
    2. 构建string(string.str = p;string.len = len;)
    3. 拷贝数据(切片中数据拷贝到新申请的内存空间)

    转换示意图:

    3.3 string转[]byte

    string也可以方便的转成byte切片,如下所示:

    func GetSliceByString(str string) []byte {
        return []byte(str)
    }
    

    string转换成byte切片,也需要一次内存拷贝,其过程如下:

    1. 申请切片内存空间
    2. 将string拷贝到切片

    转换示意图

    3.4 字符串的拼接

    字符串可以很方便的拼接,像下面这样:

    str := "str1" + "str2" + "str3"
    

    即便有非常多的字符串需要拼接,性能上也有比较好的保证,因为新字符串的内存空间是一次分配完成的,所以性能消耗主要在拷贝数据上。

    一个拼接语句的字符串编译时都会被存放到一个切片中,拼接过程需要遍历两次切片,

    1. 第一次遍历获取总的字符串长度,据此申请内存
    2. 第二次遍历会把字符串逐个拷贝过去。

    字符串拼接伪代码如下:

    func concatstrings(a []string) string { // 字符串拼接
        length := 0        // 拼接后总的字符串长度
    
        for _, str := range a {
            length += len(str)
        }
    
        s, b := rawstring(length) // 生成指定大小的字符串,返回一个string和切片,二者共享内存空间
    
        for _, str := range a {
            copy(b, str)    // string无法修改,只能通过切片修改
            b = b[len(str):]
        }
    
        return s
    }
    

    因为string是无法直接修改的,所以这里使用rawstring()方法初始化一个指定大小的string,同时返回一个切片,二者共享同一块内存空间,后面向切片中拷贝数据,也就间接修改了string。

    rawString源代码如下

    
    func rawstring(size int) (s string, b []byte) { // 生成一个新的string,返回的string和切片共享相同的空间
        p := mallocgc(uintptr(size), nil, false)  // 定义gc内存回收
    
        stringStructOf(&s).str = p
        stringStructOf(&s).len = size
    
        *(*slice)(unsafe.Pointer(&b)) = slice{p, size, size}
    
        return
    }
    

    4.知识点

    4.1为什么字符串不允许修改?

    像C++语言中的string,其本身拥有内存空间,修改string是支持的。

    但Go的实现中,string不包含内存空间,只有一个内存的指针,这样做的好处是string变得非常轻量,可以很方便的进行传递而不用担心内存拷贝。

    因为string通常指向字符串字面量,而字符串字面量存储位置是只读段,而不是堆或栈上,所以才有了string不可修改的约定。

    4.2 []byte转换成string一定会拷贝内存吗?

    byte切片转换成string的场景很多,为了性能上的考虑,有时候只是临时需要字符串的场景下,byte切片转换成string时并不会拷贝内存,而是直接返回一个string,这个string的指针(string.str)指向切片的内存。

    比如,编译器会识别如下临时场景:

    • 使用m[string(b)]来查找map(map是string为key,临时把切片b转成string);
    • 字符串拼接,如< + string(b) + >
    • 字符串比较:string(b) == foo

    因为是临时把byte切片转换成string,也就避免了因byte切片同容改成而导致string引用失败的情况,所以此时可以不必拷贝内存新建一个string。

    4.3 string和[]byte如何取舍

    string和[]byte都可以表示字符串,但因数据结构不同,其衍生出来的方法也不同,要根据实际应用场景来选择。

    string 使用场景:

    1. 需要字符串比较的场景
    2. 不需要nil字符串的场景

    []byte 的场景

    1. 修改字符串的场景, 尤其是修改粒度为1个字节
    2. 函数返回值,需要用nil表示的场景
    3. 需要切片操作的场景

    虽然看起来string适用的场景不如[]byte多,但因为string直观,在实际应用中还是大量存在,在偏底层的实现中[]byte使用更多。

    参考

    【string底存数据结构】

    ♥永远年轻,永远热泪盈眶♥
  • 相关阅读:
    LeetCode 258 Add Digits
    LeetCode 231 Power of Two
    LeetCode 28 Implement strStr()
    LeetCode 26 Remove Duplicates from Sorted Array
    LeetCode 21 Merge Two Sorted Lists
    LeetCode 20 Valid Parentheses
    图形处理函数库 ImageTTFBBox
    php一些函数
    func_get_arg(),func_get_args()和func_num_args()的用法
    人生不是故事,人生是世故,摸爬滚打才不会辜负功名尘土
  • 原文地址:https://www.cnblogs.com/failymao/p/14916800.html
Copyright © 2020-2023  润新知