本文介绍Golang的内置类型string
(字符串)的一些用法和注意事项。
文件reflect/value.go
,描述了内置类型string
的运行时结构。Data
是一个指针,Len
是长度。
type StringHeader struct {
Data uintptr
Len int
}
Golang中,string
是不可变的,多个数据可以共享同一份底层数据(Data).
// stringptr 返回 string data的指针
func stringptr(s string) uintptr {
return (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
}
func TestShare(t *testing.T) {
s1 := "1234"
s2 := s1[:2] / "12"
// s1,s2是不同字符串,指向同一份字符串数据
t.Log(stringptr(s1) == stringptr(s2)) // true
s3 := "12"
s4 := "1" + "2"
// Golang编译期间内部化了字符串常量
t.Log(stringptr(s3) == stringptr(s4)) // true
s5 := "12"
s6 := strconv.Itoa(12)
// 运行时产生的字符串不能内部化
t.Log(stringptr(s5) == stringptr(s6)) // false
}
类型转换
string
的Data
是byte
切片,string
可以和[]byte
相互转换。
slice的运行时结构如下,和StringHeader
基本一致。
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
在直接使用string
转出[]byte
,会发生内存拷贝。
func String2Bytes(s string) []byte {
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh := reflect.SliceHeader{
Data: sh.Data,
Len: sh.Len,
Cap: sh.Len,
}
return *(*[]byte)(unsafe.Pointer(&bh))
}
func Benchmark_NormalString2Bytes(b *testing.B) {
x := "Hello Gopher! Hello Gopher! Hello Gopher!"
for i := 0; i < b.N; i++ {
_ = []byte(x)
}
}
func Benchmark_String2Bytes(b *testing.B) {
x := "Hello Gopher! Hello Gopher! Hello Gopher!"
for i := 0; i < b.N; i++ {
_ = String2Bytes(x)
}
}
运行测试
go test -bench=. -benchmem -run=^Benchmark_$
goos: darwin
goarch: amd64
pkg: github.com/liangyaopei/GolangTester/str
Benchmark_NormalString2Bytes-8 27215298 40.2 ns/op 48 B/op 1 allocs/op
Benchmark_String2Bytes-8 1000000000 0.306 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/liangyaopei/GolangTester/str 1.494s
遍历
遍历有2中方式:
•for-range
: 将字符串按照rune
来解析。•下标遍历: 取到的是byte
for-range
遍历
func TestRangeStr(t *testing.T) {
const nihongo = "日本語"
for index, runeValue := range nihongo {
t.Logf("%#U starts at byte position %d\n", runeValue, index)
}
}
// === RUN TestRangeStr
// str_test.go:59: U+65E5 '日' starts at byte position 0
// str_test.go:59: U+672C '本' starts at byte position 3
// str_test.go:59: U+8A9E '語' starts at byte position 6
// --- PASS: TestRangeStr (0.00s)
下标遍历
func TestRangeStr2(t *testing.T) {
const nihongo = "日本語"
for i := 0; i < len(nihongo); i++ {
t.Logf("%v starts at byte position %d\n", nihongo[i], i)
}
}
内部化
字符串内部化(string intern)是指一种让相同的字符串在内存中只保存一份的技术。对于要存储大量字符串的应用来说,它可以显著降低内存占用。
package main
import (
"fmt"
"strconv"
)
type stringInterner map[string]string
func (si stringInterner) Intern(s string) string {
if interned, ok := si[s]; ok {
return interned
}
si[s] = s
return s
}
func main() {
si := stringInterner{}
s1 := si.Intern("12")
s2 := si.Intern(strconv.Itoa(12))
fmt.Println(stringptr(s1) == stringptr(s2)) // true
}
参考文献
1.Strings, bytes, runes and characters in Go[1]2.String interning in Go[2]
我的公众号:lyp分享的地方
我的知乎专栏: https://zhuanlan.zhihu.com/c_1275466546035740672
我的博客:www.liangyaopei.com[3]
Github Page: https://liangyaopei.github.io/
References
[1]
Strings, bytes, runes and characters in Go: https://blog.golang.org/strings[2]
String interning in Go: https://artem.krylysov.com/blog/2018/12/12/string-interning-in-go/[3]
www.liangyaopei.com: http://www.liangyaopei.com




