作者:kaining_huang_750 | 来源:互联网 | 2024-11-25 13:46
String特性
在Golang的标准库文件 src/builtin/builtin.go
中,可以找到对内置类型string的定义和描述:
// string 表示由8位字节组成的集合,通常以UTF-8编码的形式存在,但这不是强制性的。字符串可以为空(长度为0),但不能为空指针(nil),且字符串值是不可变的。
type string string
这段定义说明了string是由8位字节组成的集合,通常采用UTF-8编码,但并非必须。此外,字符串可以为空(即长度为0),但不能为nil,且字符串对象是不可变的。
在Golang中,字符串可以通过双引号或反引号进行赋值。使用双引号声明的字符串与其他编程语言中的字符串类似,仅适用于单行字符串的初始化,如果字符串中包含换行符或双引号等特殊字符,需要使用\进行转义;而使用反引号声明的字符串则不受单行限制,并且可以在字符串中直接使用特殊字符,这在编写JSON或其他数据格式时非常方便。
实现原理
数据结构
Golang的源码包 src/runtime/string.go
中定义了string的数据结构:
type stringStruct struct {
str unsafe.Pointer
len int
}
这个结构体非常简单,包含两个字段:str表示字符串的起始地址,len表示字符串的长度。
创建字符串时,首先会构建一个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 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函数将多个输入字符串复制到目标字符串所在的内存空间。当待拼接的字符串非常大时,这种复制操作可能会带来显著的性能损失。
类型转换
在处理JSON等数据格式时,经常需要在string和[]byte之间进行类型转换。
从字节数组([]byte)到字符串(string)的转换,需要使用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地址,而不进行复制。然而,需要注意的是,类型转换的开销可能比预期的要大,因此在频繁进行类型转换时,应考虑其对程序性能的影响。