golang string
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里存储的是字符按照utf8编码后的“8-bit bytes”二进制数据 。也就是byte类型
string的长度:
utf8表示一个中文需要2个字节以上的空间,表示一个英文字符需要1个字节的空间,所以当使用len()返回字符串的长度时,实际上返回的是字节的数量。
关于字符串的长度参考全面认识golang string
strings库常用函数
// 转换大小写
strings.ToUpper()
strings.ToLower()
// 字符串清理
strings.Trim()
strings.TrimPrefix()
strings.TrimSuffix()
// 字符串拆合
strings.Split()
strings.Fields()
strings.Join()
// 子串
strings.HasPrefix()
strings.Contains()
strings.Index()
strings.Count()
// Reader
strings.NewReader()
数据结构
【stringStruct】
type stringStruct struct {
str unsafe.Pointer
len int
}
- string的底层实际上是一个stringStruct结构体,结构体的定义位于src/runtime/string.go文件中
- str表示字符串的首地址
- len表示字符串的长度
####字符串类型是只读的,不可修改
字符串一旦创建就不能修改,意味着可以通过下标来获取字符串的byte,但是不能通过下标来进行赋值。但是可以用过转换成[]byte再转换成string来实现修改。
字符串拼接
字符串拼接由多种方式,分别是:
- 使用操作符 +/+= ,特点是易用
- fmt.Sprintf,性能最差,但可以格式化
- bytes.Buffer,跟strings.Join性能差不多
- strings.Join
- strings.Builder,性能最高
【runtime.concatstrings】
使用+操作符拼接字符串,会调用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 ""
}
// If there is just one string and either it is not on the stack
// or our result does not escape the calling frame (buf != nil),
// then we can return that string directly.
if count == 1 && (buf != nil || !stringDataOnStack(a[idx])) {
return a[idx]
}
s, b := rawstringtmp(buf, l) //返回string和[]byte,他们的数据地址都指向同一个新申请的地址
for _, x := range a { //遍历要拼接的字符串,将其拷贝到内存空间
copy(b, x)
b = b[len(x):]
}
return s
}
concatstrings方法在运行时,会调用copy将输入的多个字符串拷贝到目标字符串所在的内存空间。如果需要拼接的字符串非常大,需要注意拷贝所带来的性能损失。
字符串与字节切片相互转换
【runtime.slicebytetostring】将字节切片转换成字符串
// Buf is a fixed-size buffer for the result,
// it is not nil if the result does not escape.
func slicebytetostring(buf *tmpBuf, b []byte) (str string) {
l := len(b)
if l == 0 {
// Turns out to be a relatively common case.
// Consider that you want to parse out data between parens in "foo()bar",
// you find the indices and convert the subslice to string.
return ""
}
if raceenabled {
racereadrangepc(unsafe.Pointer(&b[0]),
uintptr(l),
getcallerpc(),
funcPC(slicebytetostring))
}
if msanenabled {
msanread(unsafe.Pointer(&b[0]), uintptr(l))
}
if l == 1 {
stringStructOf(&str).str = unsafe.Pointer(&staticbytes[b[0]])
stringStructOf(&str).len = 1
return
}
// 为str申请一个内存空间用来存储byte数组
var p unsafe.Pointer
if buf != nil && len(b) <= len(buf) {
p = unsafe.Pointer(buf)
} else {
p = mallocgc(uintptr(len(b)), nil, false)
}
stringStructOf(&str).str = p
stringStructOf(&str).len = len(b)
// 将切片的底层数组复制到str的内存空间
memmove(p, (*(*slice)(unsafe.Pointer(&b))).array, uintptr(len(b)))
return
}
【runtime.stringtoslicebyte】将字符串转换成byte切片
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
}
// rawbyteslice allocates a new byte slice. The byte slice is not zeroed.
func rawbyteslice(size int) (b []byte) {
cap := roundupsize(uintptr(size))
p := mallocgc(cap, nil, false)
if cap != uintptr(size) {
memclrNoHeapPointers(add(p, uintptr(size)), cap-uintptr(size))
}
*(*slice)(unsafe.Pointer(&b)) = slice{p, size, int(cap)}
return
}
分配一个新的切片空间,将string拷贝给新切片
高效使用字符串
- 字符串拼接使用strings.Builder
- 字符串与字节切片相互转换,使用unsafe.Pointer转换成字节切片实现零拷贝
总结
字符串在go里面是一片连续的内存空间,是由一个个字符组成是一个集合。
string类型的值是只读的,不可以被修改。
reference
- Go语言设计与实现-字符串
- 全面认识golang string
- Golang语言怎么高效使用字符串