上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
5.1 字符串
字符串是 不可变 字节序列,其本身是一个复合结构。
+-----------+ +---+---+---+---+---+ | pointer -|--------> | h | e | l | l | o | +-----------+ +---+---+---+---+---+ | len = 5 | +-----------+ [...]byte, UTF-8 header
// runtime/string.go type stringStruct struct { str unsafe.Pointer len int } type stringStructDWARF struct { str *byte len int }
- 编码
UTF-8
,无NULL
结尾,默认值""
。
- 使用
`raw string`
定义原始字符串。 - 支持
!=
、==
、<
、<=
、>=
、>
、+
、+=
。
- 索引访问字节数组(非字符),不能获取元素地址。
- 切片返回子串,依旧指向原数组。
- 内置函数
len
返回字节数组长度。
func main() { s := "雨痕\x61\142\u0041" bs := []byte(s) rs := []rune(s) // rune/int32: unicode code point fmt.Printf("% X, %d\n", s, len(s)) fmt.Printf("% X, %d\n", bs, utf8.RuneCount(bs)) fmt.Printf("%U, %d\n", rs, utf8.RuneCountInString(s)) } // E9 9B A8 E7 97 95 61 62 41, 9 // E9 9B A8 E7 97 95 61 62 41, 5 // [U+96E8 U+75D5 U+0061 U+0062 U+0041], 5
func main() { s := "雨痕 abc" fmt.Printf("%X\n", s[1]) // 9B // println(&s[1]) // invalid operation: cannot take address of s[1] }
func main() { var s string println(s == "") // true // println(s == nil) // invalid operation: mismatched types string and untyped nil }
原始字符串(raw string)内的转义、换行、前置空格、注释等,都视作内容,不与处理。
func main() { s := `line\r\n, line 2` println(s) // raw string } /* line\r\n, line 2 */
以加号连接字面量时,注意操作符位置。
func main() { s := "ab" + // 跨行时,加法操作符必须在上行结尾。 "cd" println(s == "abcd") // true println(s > "abc") // true }
子串内部指针依旧指向原字节数组。
func main() { s := "hello, world!" s2 := s[:4] p1 := (*reflect.StringHeader)(unsafe.Pointer(&s)) p2 := (*reflect.StringHeader)(unsafe.Pointer(&s2)) fmt.Printf("%#v, %#v\n", p1, p2) } // &StringHeader{ Data:0x497208, Len:13 } // &StringHeader{ Data:0x497208, Len:4 }
遍历,分 byte
和 rune
两种方式。
func main() { s := "雨痕" // byte for i := 0; i < len(s); i++ { fmt.Printf("%d: %X\n", i, s[i]) } // rune for i, c := range s { fmt.Printf("%d: %U\n", i, c) } } /* 0: E9 1: 9B 2: A8 3: E7 4: 97 5: 95 0: U+96E8 3: U+75D5 */
可作 append
、 copy
参数。
func main() { s := "de" bs := make([]byte, 0) bs = append(bs, "abc"...) bs = append(bs, s...) buf := make([]byte, 5) copy(buf, "abc") copy(buf[3:], s) fmt.Printf("%s\n", bs) // abcde fmt.Printf("%s\n", buf) // abcde }
标准库相关:
bytes
:字节切片。fmt
:格式化。strconv
:转换。strings
:函数。text
:文本(模版)。unicode
:码点。
转换
可在 rune
、 byte
、 string
间转换。
单引号字符字面量是
rune
类型,代表 Unicode 字符。
func main() { var r rune = '我' var s string = string(r) var b byte = byte(r) var s2 string = string(b) var r2 rune = rune(b) fmt.Printf("%c, %U\n", r, r) fmt.Printf("%s, %X, %X, %X\n", s, b, s2, r2) } // 我, U+6211 // 我, 11, 11, 11
要修改字符串,须转换为可变类型( []rune
或 []byte
),待完成后再转换回来。
但不管如何转换,都需重新分配内存,并复制数据。
字面量分配于 RODATA,其内存不能修改。
func main() { s := strings.Repeat("a", 1<<10) // 分配内存、复制。 bs := []byte(s) bs[1] = 'B' // 分配内存、复制。 s2 := string(bs) hs := (*reflect.StringHeader)(unsafe.Pointer(&s)) hbs := (*reflect.SliceHeader)(unsafe.Pointer(&bs)) hs2 := (*reflect.StringHeader)(unsafe.Pointer(&s2)) fmt.Printf("%#v\n%#v\n%#v\n", hs, hbs, hs2) } // &StringHeader{ Data:0xc0000a8000, Len:1024 } // &SliceHeader { Data:0xc0000a8400, Len:1024, Cap:1024 } // &StringHeader{ Data:0xc0000a8800, Len:1024 } // 0x400 = 1024
验证编码是否正确。
func main() { s := "雨痕" s2 := string(s[0:2] + s[4:]) // 非法拼接。 fmt.Printf("% X, % X\n", s, s2) fmt.Println(utf8.ValidString(s2)) } // E9 9B A8 E7 97 95, E9 9B 97 95 // false
5.1.1 性能
使用 “不安全” 方法进行转换,改善性能。
- 不动底层数组,直接构建
string
或slice
头。 - 如果修改,注意内存安全。
slice { data, len, cap }
->string { data, len }
slice { string.data, string.len, string.len }
package main import ( "testing" "strings" "unsafe" ) var S = strings.Repeat("a", 100) func normalConv() bool { b := []byte(S) s2 := string(b) return s2 == S } func unsafeConv() bool { // []byte(s) b := unsafe.Slice(unsafe.StringData(S), len(S)) // string(b) s2 := unsafe.String(unsafe.SliceData(b), len(b)) return s2 == S } // ---------------------------------- func BenchmarkNormal(b *testing.B) { for i := 0; i < b.N; i++ { if !normalConv() { b.Fatal() } } } func BenchmarkUnsafe(b *testing.B) { for i := 0; i < b.N; i++ { if !unsafeConv() { b.Fatal() } } }
$ go test -bench . -benchmem -v cpu: Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz BenchmarkNormal-2 7285071 148 ns/op 224 B/op 2 allocs/op BenchmarkUnsafe-2 274104459 2 ns/op 0 B/op 0 allocs/op PASS
普通转换
normalConv
调用runtime.stringtoslicebyte
、runtime.slicebytetostring
,引发
mallocgc
、memmove
等操作。
动态构建字符串也容易造成性能问题。
加法操作符拼接字符串,每次都需重新分配内存和复制数据。
改进方法是预分配内存,然后一次性返回。
package main import ( "testing" "bytes" "strings" ) const N = 1000 const C = "a" var S = strings.Repeat(C, N) func concat() bool { var s2 string for i := 0; i < N; i++ { s2 += C } return s2 == S } func join() bool { b := make([]string, N) for i := 0; i < N; i++ { b[i] = C } return strings.Join(b, "") == S } func buffer() bool { var b bytes.Buffer b.Grow(N) for i := 0; i < N; i++ { b.WriteString(C) } return b.String() == S } // ---------------------------------- func BenchmarkConcat(b *testing.B) { for i := 0; i < b.N; i++ { if !concat() { b.Fatal() } } } func BenchmarkJoin(b *testing.B) { for i := 0; i < b.N; i++ { if !join() { b.Fatal() } } } func BenchmarkBuffer(b *testing.B) { for i := 0; i < b.N; i++ { if !buffer() { b.Fatal() } } }
$ go test -bench . -benchmem -v BenchmarkConcat-2 2524 419118 ns/op 530275 B/op 999 allocs/op BenchmarkJoin-2 101426 11733 ns/op 1024 B/op 1 allocs/op BenchmarkBuffer-2 148773 7334 ns/op 2048 B/op 2 allocs/op PASS
查看 strings.Join
实现,和上面的 buffer
类似。
// strings/strings.go func Join(elems []string, sep string) string { switch len(elems) { case 0: return "" case 1: return elems[0] } // 计算总长度。 n := len(sep) * (len(elems) - 1) for i := 0; i < len(elems); i++ { n += len(elems[i]) } // 预分配,循环写入。 var b Builder b.Grow(n) b.WriteString(elems[0]) for _, s := range elems[1:] { b.WriteString(sep) b.WriteString(s) } return b.String() }
编译器对字面量
+
号拼接会优化。如此之外,还可以用
fmt.Sprintf
、text/template
等方式进行。
字符串某些看似简单的操作,都可能引发堆内存分配和复制。
事实上,编译器和运行时也会采取非常规手段进行优化。
对 unsafe
的莫名抵触,大可不必!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论