乐趣区

关于golang:Go进阶数据结构string

个性

从规范库文件 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 编码的文本。另外,
string 能够为空(长度为 0),但不会是 nil,并且 string 对象不可批改。

字符串能够应用双引号赋值,也能够应用反单引号赋值。应用双引号申明的字符串和其余语言中的字符串没有太多的区别,它只能用于单行字符串的初始化,如果字符串外部呈现换行符或双引号等特殊符号,须要应用 \ 符号本义;而反引号申明的字符串能够解脱单行的限度,并且能够在字符串外部间接应用特殊符号,在遇到须要手写 JSON 或者其余简单数据格式的场景下十分不便。

实现原理

数据结构

源码包 src/runtime/string.go:stringStruct 定义了 string 的数据结构:

type stringStruct struct {
    str unsafe.Pointer
    len int
}

构造很简略,两个字段别离示意字符串的首地址和长度。

生成字符串时,会先构建 stringStruct 对象,再转换成 string,代码如下:

func gostringnocopy(str *byte) string {ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}
    s := *(*string)(unsafe.Pointer(&ss))
    return s
}

相干操作

字符串拼接

在 runtime 包中,应用 concatstrings 函数来拼接字符串,所有待拼接字符串被组织到一个切片中传入,外围源码如下:

func concatstrings(buf *tmpBuf, a []string) string {
    // 计算带拼接字符串切片长度及个数,以此申请内存
    idx := 0
    l := 0
    count := 0
    for i, x := range a {n := len(x)
        if n == 0 {continue}
        if l+n < l {throw("string concatenation too long")
        }
        l += n
        count++
        idx = i
    }
    if count == 0 {return ""}

    // 如果非空字符串的数量为 1 且以后字符串不在栈上,间接返回该字符串
    if count == 1 && (buf != nil || !stringDataOnStack(a[idx])) {return a[idx]
    }
    // 分配内存,结构一个字符串和切片,二者共享内存
    s, b := rawstringtmp(buf, l)
    // 向切片中拷贝待拼接字符串
    for _, x := range a {copy(b, x)
        b = b[len(x):]
    }
    // 返回拼接后字符串
    return s
}

须要留神的是,在失常状况下,运行时会调用 copy 将输出的多个字符串拷贝到指标字符串所在的内存空间。一旦须要拼接的字符串十分大,拷贝带来的性能损失是无奈疏忽的。

类型转换

当咱们应用 Go 语言解析和序列化 JSON 等数据格式时,常常须要将数据在 string 和 []byte 之间来回转换。

从字节数组到字符串的转换须要应用 slicebytetostring 函数,外围源码如下:

func slicebytetostring(buf *tmpBuf, ptr *byte, n int) (str string) {
    // 字节数组长度为 0 或 1 时非凡解决
    if n == 0 {return ""}
    if n == 1 {p := unsafe.Pointer(&staticuint64s[*ptr])
        if sys.BigEndian {p = add(p, 7)
        }
        stringStructOf(&str).str = p
        stringStructOf(&str).len = 1
        return
    }

    var p unsafe.Pointer
    // 依据传入的缓冲区大小决定是否须要为新字符串分配内存空间
    if buf != nil && n <= len(buf) {p = unsafe.Pointer(buf)
    } else {p = mallocgc(uintptr(n), nil, false)
    }
    stringStructOf(&str).str = p
    stringStructOf(&str).len = n
    // 将原 []byte 中的字节全副复制到新的内存空间中
    memmove(p, unsafe.Pointer(ptr), uintptr(n))
    return
}

当咱们想要将字符串转换成 []byte 类型时,须要应用 stringtoslicebyte 函数,该函数的实现非常容易了解:

func stringtoslicebyte(buf *tmpBuf, s string) []byte {var b []byte
    // 当传入缓冲区并且空间足够时,从该缓冲区切取字符串长度大小切片,否则结构一个切片
    if buf != nil && len(s) <= len(buf) {*buf = tmpBuf{}
        b = buf[:len(s)]
    } else {b = rawbyteslice(len(s))
    }
    // 将字符串复制到切片中
    copy(b, s)
    return b
}

[]byte 转换成 string 的场景有很多,出于性能上的思考,有时候只是长期须要字符串的情景下,此时不会产生拷贝,而是间接返回一个 string,其中的指针指向 []byte 的地址。而且,咱们需谨记:类型转换的开销并没有设想中那么小,常常会成为程序的性能热点。

退出移动版