高效拼接字符串
拼接字符串的方式
-
- fmt.Sprintf
- bytes.buffer
- strings.builder
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func randomString(n int) string { b := make([]byte, n) for i := range b { b[i] = letterBytes[rand.Intn(len(letterBytes))] } return string(b) }
func plusConcat(n int, str string) string { s := "" for i := 0; i < n; i++ { s += str } return s }
func sprintfConcat(n int, str string) string { s := "" for i := 0; i < n; i++ { s = fmt.Sprintf("%s%s", s, str) } return s }
func builderConcat(n int, str string) string { var builder strings.Builder for i := 0; i < n; i++ { builder.WriteString(str) } return builder.String() }
func bufferConcat(n int, s string) string { buf := new(bytes.Buffer) for i := 0; i < n; i++ { buf.WriteString(s) } return buf.String() }
func byteConcat(n int, str string) string { buf := make([]byte, 0) for i := 0; i < n; i++ { buf = append(buf, str...) } return string(buf) }
func preByteConcat(n int, str string) string { buf := make([]byte, 0, n*len(str)) for i := 0; i < n; i++ { buf = append(buf, str...) } return string(buf) }
|
benchmark 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func benchmark(b *testing.B, f func(int, string) string) { var str = randomString(10) for i := 0; i < b.N; i++ { f(10000, str) } }
func BenchmarkPlusConcat(b *testing.B) { benchmark(b, plusConcat) } func BenchmarkSprintfConcat(b *testing.B) { benchmark(b, sprintfConcat) } func BenchmarkBuilderConcat(b *testing.B) { benchmark(b, builderConcat) } func BenchmarkBufferConcat(b *testing.B) { benchmark(b, bufferConcat) } func BenchmarkByteConcat(b *testing.B) { benchmark(b, byteConcat) } func BenchmarkPreByteConcat(b *testing.B) { benchmark(b, preByteConcat) }
|
测试结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Forrest@LAPTOP-32G4HDVI MINGW64 /d/Go_WorkSpace/面经/connectStrings (main) $ go test -bench="Concat$" -benchmem . goos: windows goarch: amd64 pkg: demo cpu: AMD Ryzen 7 5800H with Radeon Graphics BenchmarkPlusConcat-16 16 79042431 ns/op 530998053 B/op 10024 allocs/op BenchmarkSprintfConcat-16 9 115482900 ns/op 833870728 B/op 34211 allocs/op BenchmarkBuilderConcat-16 9648 109838 ns/op 514801 B/op 23 allocs/op BenchmarkBufferConcat-16 12285 95907 ns/op 368576 B/op 13 allocs/op BenchmarkByteConcat-16 10000 122219 ns/op 621297 B/op 24 allocs/op BenchmarkPreByteConcat-16 21231 59183 ns/op 212993 B/op 2 allocs/op PASS ok demo 8.840s
|
其中的 -bench=Concat$
中的$
表示行尾,因此这将匹配以”Concat”结尾的测试函数。
拼接大量字符串优先使用 stirngs.builder
strings.builder 的扩容策略
1 2 3 4 5 6 7 8 9 10 11 12
| func TestBuilderCap(t *testing.T) { str := randomString(10) var builder strings.Builder cap := 0 for i := 0; i < 100000; i++ { builder.WriteString(str) if cap != builder.Cap() { cap = builder.Cap() fmt.Printf("%d ", cap) } } }
|
运行结果
1 2 3 4 5 6
| Forrest@LAPTOP-32G4HDVI MINGW64 /d/Go_WorkSpace/面经/connectStrings (main) $ go test -v === RUN TestBuilderConcat 16 32 64 128 256 512 896 1408 2048 3072 4096 5376 6912 9472 12288 16384 21760 28672 40960 57344 73728 98304 131072 --- PASS: TestBuilderConcat (0.00s) PASS ok demo 0.709s
|
go 中的slice是 可变长的数据序列。
go 是值传递,其中func中的 参数也是值传递,只是传递的是 pointer,同一个pointer,所以更改后会对原slice的值造成影响
其中由 指向array的指针+len+cap 三个字段构成slice。
扩容策略:
- 若容量超过系统限制,直接panic
- 若容量为0,返回 zerobase
slice{unsafe.Pointer($zerobase),old.len,size}
- 当cap小于 256时,扩容机制为:直接翻倍;
- 当cap大于256时,扩容策 略为:在原容量 n 的基础上循环执行 n += (n+3*256)/4,直到 n 大于等于预期新容量,并取 n 作为新容量。即:扩容1/4 +192 的固定值
- 计算相应的数据结构所需的内存大小,分配内存
- 数据迁移
- 返回新slice
1 2 3 4 5
| func Test_slice(t *testing.T){ s := make([]int,512) s = append(s,1) t.Logf("len of s: %d, cap of s: %d",len(s),cap(s)) }
|