Rumah > Artikel > pembangunan bahagian belakang > Go String 解析
什么是字符串?
在 Go 中,字符串是一个 (可能为空) 不可变的字节序列。对于我们来说,这里的关键词是 不可变。因为字节片是可变的,所以在 string 和 []byte 之间进行转换通常需要分配和复制,这是很昂贵的。
在幕后,Go 的字符串 (当前) 表示为 长度和指向字符串数据的指针.
什么是字符串驻留?
考虑这段代码:
b := []byte("hello") s := string(b) t := string(b)
s 和 t 是字符串,因此它们都有长度和数据指针。它们的长度显然是相同的。那它们的数据指针呢?
Go 语言无法为我们提供直接的查找方法。但是我们可以使用 unsafe 来探查:
func pointer(s string) uintptr { p := unsafe.Pointer(&s) h := *(*reflect.StringHeader)(p) return h.Data }
(此函数应返回 unsafe.Pointer。详见 Go 问题 19367。)
如果我们 fmt.Println(pointer(s), pointer(t)),我们会得到类似 4302664 4302632 的信息。指针是不同的;它们有两个单独的数据副本 hello。
(这是一个练习链接。如果你想要尝试,将 "hello" 变成 "h" 会发生什么情况?解释 )
假设您希望重新使用数据 hello 的单个副本?这就是字符串驻留。字符串驻留有两个优点。明显的一个优点是,你不需要分配和复制数据。另一个优点是它加快了字符串相等性检查的速度。如果两个字符串具有相同的长度和相同的数据指针,则它们是相等的;没有必要检查字节。
从 Go 1.14 开始,Go 不会驻留大多数字符串。与其它形式的缓存一样,驻留也有成本:并发安全性的同步,垃圾收集器的复杂性,以及每次创建字符串时要执行的额外代码。而且,就像缓存一样,在某些情况下它是有害的,而不是有用的。如果你在处理字典里的单词,则任何单词都不会出现两次,这时,字符串驻留既浪费时间又浪费内存。
手动字符串驻留
可以在 Go 中手动驻留字符串。我们需要的是一种在给定字节切片 (byte slice) 的情况下寻找现有字符串以重新使用的方法,也许使用诸如 map[[]byte]string 之类的方法。如果查找成功,则使用现有字符串;如果失败,我们将转换并存储该字符串以备将来使用。
这里只有一个问题:您不能使用 []byte 作为 map 的键。
多亏了长期的编译器优化,我们可以使用 map[string]string 代替。这里有一个优化,键是转换后字节切片的 map 操作实际上不会生成在查找期间会用到的新字符串。
m := make(map[string]string) b := []byte("hello") s := string(b) // 分配了 _ = m[string(b)] // 不分配!
(类似的优化适用于其他情况,在这些情况下,编译器可以证明转换后的字节切片在使用过程中不会被修改,例如 switch string(b),当所有 switch 情况都没有副作用时。)
驻留字符串所需的全部代码是这样的:
func intern(m map[string]string, b []byte) string { // 查找一个存在的字符串来重用 c, ok := m[string(b)] if ok { // 找到一个存在的字符串 return c } // 没有找到,所以制作一个并且存储它 s := string(b) m[s] = s return s }
很简单
新出现的困难(并发症)
请注意,这个手动驻留例程将驻留问题推入了调用代码。您需要管理对 map 的并发访问;您需要确定 map (以及其中的所有内容) 的生命周期;并且您每次需要字符串时都需要付出 map 查找的额外费用。
将这些决定推到调用代码上可以产生更好的性能。例如,假设您正在将 json 解码为 map[string]interface{}。json 解码器可能不是并发的。map 的生命周期可以绑定到 json 解码器。并且此 map 的键很可能会经常重复,这是字符串驻留的最佳情况;这使得额外的 map 查找成本值得。
一个助手包
如果您不想考虑这些并发症中的任何一个,并且愿意接受轻微的性能损失,并且有字符串驻留可能会有所帮助的代码,则有一个为此的包:github.com/josharian/intern。
它的工作原理是可怕的滥用 sync.Pool。它将驻留 maps 存储在 sync.Pool 中,根据需要检索它们。这很好的解决了并发访问问题,因为 sync.Pool 的访问是并发安全的。它主要解决了生存期问题,因为在 sync.Pool 中的内容通常最终会被垃圾收集。(有关管理生存期的相关阅读,请参阅 Go issue 29696。)
Atas ialah kandungan terperinci Go String 解析. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!