Random walk to my blog

my blog for sharing my knowledge,experience and viewpoint

0%

Golang的string解析

本文介绍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
// 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
}

类型转换

stringDatabyte切片,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)
}
}

// === 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)

下标遍历

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)) // true
}

参考文献

  1. Strings, bytes, runes and characters in Go
  2. String interning in Go

我的公众号:lyp分享的地方

我的知乎专栏: https://zhuanlan.zhihu.com/c_1275466546035740672

我的博客:www.liangyaopei.com

Github Page: https://liangyaopei.github.io/