本文介绍Golang的内置类型string
(字符串)的一些用法和注意事项。
文件reflect/value.go
,描述了内置类型string
的运行时结构。Data
是一个指针,Len
是长度。
1 2 3 4
| type StringHeader struct { Data uintptr Len int }
|
Golang中,string
是不可变的,多个数据可以共享同一份底层数据(Data).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| func stringptr(s string) uintptr { return (*reflect.StringHeader)(unsafe.Pointer(&s)).Data }
func TestShare(t *testing.T) { s1 := "1234" s2 := s1[:2] t.Log(stringptr(s1) == stringptr(s2))
s3 := "12" s4 := "1" + "2" t.Log(stringptr(s3) == stringptr(s4))
s5 := "12" s6 := strconv.Itoa(12) t.Log(stringptr(s5) == stringptr(s6)) }
|
类型转换
string
的Data
是byte
切片,string
可以和[]byte
相互转换。
slice的运行时结构如下,和StringHeader
基本一致。
1 2 3 4 5
| type SliceHeader struct { Data uintptr Len int Cap int }
|
在直接使用string
转出[]byte
,会发生内存拷贝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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) } }
|
运行测试
1 2 3 4 5 6 7 8 9 10
| 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
遍历
1 2 3 4 5 6 7 8 9 10 11 12
| func TestRangeStr(t *testing.T) { const nihongo = "日本語" for index, runeValue := range nihongo { t.Logf("%#U starts at byte position %d\n", runeValue, index) } }
|
下标遍历
1 2 3 4 5 6
| 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)是指一种让相同的字符串在内存中只保存一份的技术。对于要存储大量字符串的应用来说,它可以显著降低内存占用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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)) }
|
参考文献
- Strings, bytes, runes and characters in Go
- String interning in Go
我的公众号:lyp分享的地方
我的知乎专栏: https://zhuanlan.zhihu.com/c_1275466546035740672
我的博客:www.liangyaopei.com
Github Page: https://liangyaopei.github.io/