golang slice 和 string 重用
相比于 c/c++,golang 的一个很大的改进就是引入了 gc 机制,不再需要用户自己管理内存,大大减少了程序由于内存泄露而引入的 bug,但是同时 gc 也带来了额外的性能开销,有时甚至会因为使用不当,导致 gc 成为性能瓶颈,所以 golang 程序设计的时候,应特别注意对象的重用,以减少 gc 的压力。而 slice 和 string 是 golang 的基本类型,了解这些基本类型的内部机制,有助于我们更好地重用这些对象 slice 和 string 内部结构slice 和 string 的内部结构可以在 type StringHeader struct { Data uintptr Len int } type SliceHeader struct { Data uintptr Len int Cap int } 可以看到一个 string 包含一个数据指针和一个长度,长度是不可变的 slice 包含一个数据指针、一个长度和一个容量,当容量不够时会重新申请新的内存,Data 指针将指向新的地址,原来的地址空间将被释放 从这些结构就可以看出,string 和 slice 的赋值,包括当做参数传递,和自定义的结构体一样,都仅仅是 Data 指针的浅拷贝 slice 重用append 操作si1 := []int{1,2,3,4,5,6,7,8,9} si2 := si1 si2 = append(si2,0) Convey("重新分配内存",func() { header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1)) header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2)) fmt.Println(header1.Data) fmt.Println(header2.Data) So(header1.Data,ShouldNotEqual,header2.Data) }) si1 和 si2 开始都指向同一个数组,当对 si2 执行 append 操作时,由于原来的 Cap 值不够了,需要重新申请新的空间,因此 Data 值发生了变化,在 内存重用si1 := []int{1,9} si2 := si1[:7] Convey("不重新分配内存",ShouldEqual,header2.Data) }) Convey("往切片里面 append 一个值",func() { si2 = append(si2,10) Convey("改变了原 slice 的值",func() { header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1)) header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2)) fmt.Println(header1.Data) fmt.Println(header2.Data) So(header1.Data,header2.Data) So(si1[7],10) }) }) si2 是 si1 的一个切片,从第一段代码可以看到切片并不重新分配内存,si2 和 si1 的 Data 指针指向同一片地址,而第二段代码可以看出,当我们往 si2 里面 append 一个新的值的时候,我们发现仍然没有内存分配,而且这个操作使得 si1 的值也发生了改变,因为两者本就是指向同一片 Data 区域,利用这个特性,我们只需要让 PS: 你可以使用 stringConvey("字符串常量",func() { str1 := "hello world" str2 := "hello world" Convey("地址相同",func() { header1 := (*reflect.StringHeader)(unsafe.Pointer(&str1)) header2 := (*reflect.StringHeader)(unsafe.Pointer(&str2)) fmt.Println(header1.Data) fmt.Println(header2.Data) So(header1.Data,header2.Data) }) }) 这个例子比较简单,字符串常量使用的是同一片地址区域 Convey("相同字符串的不同子串",func() { str1 := "hello world"[:6] str2 := "hello world"[:5] Convey("地址相同",func() { header1 := (*reflect.StringHeader)(unsafe.Pointer(&str1)) header2 := (*reflect.StringHeader)(unsafe.Pointer(&str2)) fmt.Println(header1.Data,str1) fmt.Println(header2.Data,str2) So(str1,str2) So(header1.Data,header2.Data) }) }) 相同字符串的不同子串,不会额外申请新的内存,但是要注意的是这里的相同字符串,指的是 Convey("不同字符串的相同子串",func() { str1 := "hello world"[:5] str2 := "hello golang"[:5] Convey("地址不同",header2.Data) }) }) 实际上对于字符串,你只需要记住一点,字符串是不可变的,任何字符串的操作都不会申请额外的内存(对于仅内部数据指针而言),我曾自作聪明地设计了一个 cache 去存储字符串,以减少重复字符串所占用的空间,事实上,除非这个字符串本身就是由 参考链接
转载请注明出处 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |