Go1.18 快讯:新增字符串 Clone API
发布于 2021-11-04 15:06 ,所属分类:软件编程学习资料
阅读本文大概需要 5 分钟。
大家好,我是 polarisxu。
Go 1.18 虽然还有 4 个月发布,但大部分的功能基本确定。我们可以提前知晓、熟悉。
今天介绍的是标准库中新增的一个 API:strings.Clone()
。
从名称可以知道,这是克隆。很多其他语言一开始就有这样的功能。比如 PHP 有 clone 关键字、__clone
魔术方法;Java 的根类 Object 有 clone 方法等。
01 函数签名
该函数的定义如下(见:https://pkg.go.dev/strings@master#Clone)
//Clonereturnsafreshcopyofs.
//Itguaranteestomakeacopyofsintoanewallocation,
//whichcanbeimportantwhenretainingonlyasmallsubstring
//ofamuchlargerstring.UsingClonecanhelpsuchprograms
//uselessmemory.Ofcourse,sinceusingClonemakesacopy,
//overuseofClonecanmakeprogramsusemorememory.
//Cloneshouldtypicallybeusedonlyrarely,andonlywhen
//profilingindicatesthatitisneeded.
//Forstringsoflengthzerothestring""willbereturned
//andnoallocationismade.
funcClone(sstring)string
Clone 返回 s 的新副本。它保证将 s 复制到一个新分配的副本中,当只保留一个很大的字符串中的一个小子字符串时,这一点很重要。使用克隆可以帮助这些程序使用更少的内存。当然,由于使用克隆制作拷贝,过度使用克隆会使程序使用更多内存。通常,只有在分析表明需要克隆时,才谨慎使用克隆。对于长度为零的字符串,将返回字符串 ""
,不进行内存分配。
02 举例说明
大家可能还是迷惑,不知道有啥用。举一个代码例子说明:
packagemain
import(
"fmt"
"reflect"
"unsafe"
)
funcmain(){
s:="abcdefghijklmn"
s1:=s[:4]
sHeader:=(*reflect.StringHeader)(unsafe.Pointer(&s))
s1Header:=(*reflect.StringHeader)(unsafe.Pointer(&s1))
fmt.Println(sHeader.Len==s1Header.Len)
fmt.Println(sHeader.Data==s1Header.Data)
//Output:
//false
//true
}
Len 不相等不需要解释,Data 相等就值得注意。
上面代码,有些人可能不知道什么意思。这里涉及到 Go 中 string 类型的底层结构。在 Go 中,string 类型的底层表示如下:
typestringstruct{
ptrunsafe.Pointer
lenint
}而 reflect.StringHeader 结构是对字符串底层结构的反射表示。
在上面示例场景中,如果 s 很大,而之后我们只需要使用它的某个短子串,这会导致内存的浪费,因为子串和原字符串的 Data 部分指向相同的内存,因此整个字符串并不会被 GC 回收。
strings.Clone
函数就是为了解决这个问题的:(要正常运行下面代码,需要按照 Go tip 版本)
s2:=strings.Clone(s[:4])
s2Header:=(*reflect.StringHeader)(unsafe.Pointer(&s2))
fmt.Println(sHeader.Len==s2Header.Len)
fmt.Println(sHeader.Data==s2Header.Data)
//Output:
//false
//false
通过克隆得到 s2,从最后输出结果看,Data 已经不同了,原始的长字符串就可以被垃圾回收了。(你也可以将传递给 Clone 的参数改为 s1,后面部分用 s1 和 s2 比)
03 内部实现
知道了克隆的用途,再看看 strings.Clone 的实现。
funcClone(sstring)string{
iflen(s)==0{
return""
}
b:=make([]byte,len(s))
copy(b,s)
return*(*string)(unsafe.Pointer(&b))
}
这里有两个关键点:
通过 copy 进行拷贝。其实普通的 slice,也会有需要克隆的场景,这时,需要我们手动执行 copy 操作。 return 后面的语句 *(*string)(unsafe.Pointer(&b))
,实现 []byte 到 string 的零内存拷贝转换。
04 总结
Go 虽然有 GC,大部分时候不需要考虑内存问题,但对内存的使用,我们需要有敬畏之心,特别是大块内存、重复分配内存的场景,我们需要知晓如何优化,写出真正高质量的代码。
strings.Clone 的使用很简单,但希望通过本文,你在写 Go 代码时,对类似场景下,slice 的正确使用有启发(string 可以认为是特殊的 slice)。
Go:如何获得项目根目录?
我是 polarisxu,北大硕士毕业,曾在 360 等知名互联网公司工作,10多年技术研发与架构经验!2012 年接触 Go 语言并创建了 Go 语言中文网!著有《Go语言编程之旅》、开源图书《Go语言标准库》等。
坚持输出技术(包括 Go、Rust 等技术)、职场心得和创业感悟!欢迎「polarisxu」一起成长!也欢迎加我好友交流:gopherstudio
相关资源