Golang高性能编程之string篇

250 阅读2分钟

Golang高性能编程之string篇

clean code that works.

为什么不能对string使用cap()函数

str := "hello"
fmt.Println(len(str)) // output 5
fmt.Println(cap(str)) // complier error, string内部结构是没有cap字段的

string内部结构是什么样的呢?

image.png

可以清楚看到,只有数据指针和len,没有cap字段

如何将string转换成[]byte

case 1: 直接强转,会导致内存拷贝;转换后的切片是可编辑的

func main() {
    str := "hello"
    strHeader := (*reflect.StringHeader)(unsafe.Pointer(&str))
    fmt.Printf("string\tdata:0x%x\tlen:%d\n", strHeader.Data, strHeader.Len)
    
    b := []byte(str) // 会进行内存拷贝
    sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b))
    fmt.Printf("slice\tdata:0x%x\tlen:%d\tcap:%d\n", sliceHeader.Data, sliceHeader.Len, sliceHeader.Cap)
    
    b[0] = 'a' // 可以进行赋值
}

// output
string  data:0x100f251e3        len:5
slice   data:0x140002afec0      len:5   cap:32

// 从以上输出上看,地址是不同的

case 2: 不进行内存拷贝的转换

func main() {
    str := "hello" // str的存储地址是文字常量区是只读的
    strHeader := (*reflect.StringHeader)(unsafe.Pointer(&str))
    fmt.Printf("string\tdata:0x%x\tlen:%d\n", strHeader.Data, strHeader.Len)

    b := *(*[]byte)(unsafe.Pointer(&str))
    sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b))
    fmt.Printf("slice\tdata:0x%x\tlen:%d\tcap:%d\n", sliceHeader.Data, sliceHeader.Len, sliceHeader.Cap)
    b[0] = 'a' // panic: unexpected fault address
}

// output: 
string  data:0x10521d1e3        len:5
slice   data:0x10521d1e3        len:5   cap:4377373460
unexpected fault address 0x10521d1e3

// 注意看data地址,发现地址是一致的;之所以会panic,是因为str使用的是文字常量区地址(Text Segment),禁止写入

疑问:string的底层结构和slice的底层结构不同(slice的结构比string多了一个cap字段),为什么可以强转成功?

答:因为slice前两个字段和string前两个字段是类型是一致的,在内存布局上,是可以转换的;至于cap字段,由于没有string没有,所以,值是为定义;并且,len和cap是值传递,并不会导致string后续的内存被slice对象占用,所以是安全的

将[]byte转成string,官方源码中的事例

go1.16 file: /go/src/strings/builder.go:47

image.png

Data Segment

image.png

Text Segment也称为Code Segment,用于存储常量和代码