找装修公司去哪个网站,硬件开发工程师需要掌握的专业知识,应用市场app下载安装,房地产门户网站前面提到过#xff0c;Go原生支持通过/操作符来连接多个字符串以构造一个更长的字符串#xff0c;并且通过/操作符的字符串连接构造是最自然、开发体验最好的一种。
但Go还提供了其他一些构造字符串的方法#xff0c;比如#xff1a; ● 使用fmt.Sprintf#xff1b; ● 使…前面提到过Go原生支持通过/操作符来连接多个字符串以构造一个更长的字符串并且通过/操作符的字符串连接构造是最自然、开发体验最好的一种。
但Go还提供了其他一些构造字符串的方法比如 ● 使用fmt.Sprintf ● 使用strings.Join ● 使用strings.Builder ● 使用bytes.Buffer。 在这些方法中哪种方法最为高效呢我们使用基准测试的数据作为参考
var sl []string []string{
Rob Pike ,
Robert Griesemer ,
Ken Thompson ,
}
func concatStringByOperator(sl []string) string {
var s string
for _, v : range sl {
s v
}
return s
}
func concatStringBySprintf(sl []string) string {
var s string
for _, v : range sl {
s fmt.Sprintf(%s%s, s, v)
}
return s
}
func concatStringByJoin(sl []string) string {
return strings.Join(sl, )
}
func concatStringByStringsBuilder(sl []string) string {
var b strings.Builder
for _, v : range sl {
b.WriteString(v)
}
return b.String()
}
func concatStringByStringsBuilderWithInitSize(sl []string) string {
var b strings.Builder
b.Grow(64)for _, v : range sl {
b.WriteString(v)
}
return b.String()
}
func concatStringByBytesBuffer(sl []string) string {
var b bytes.Buffer
for _, v : range sl {
b.WriteString(v)
}
return b.String()
}
func concatStringByBytesBufferWithInitSize(sl []string) string {
buf : make([]byte, 0, 64)
b : bytes.NewBuffer(buf)
for _, v : range sl {
b.WriteString(v)
}
return b.String()
}
func BenchmarkConcatStringByOperator(b *testing.B) {
for n : 0; n b.N; n {
concatStringByOperator(sl)
}
}
func BenchmarkConcatStringBySprintf(b *testing.B) {
for n : 0; n b.N; n {
concatStringBySprintf(sl)
}
}
func BenchmarkConcatStringByJoin(b *testing.B) {
for n : 0; n b.N; n {
concatStringByJoin(sl)
}
}
func BenchmarkConcatStringByStringsBuilder(b *testing.B) {
for n : 0; n b.N; n {
concatStringByStringsBuilder(sl)
}
}
func BenchmarkConcatStringByStringsBuilderWithInitSize(b *testing.B) {
for n : 0; n b.N; n {
concatStringByStringsBuilderWithInitSize(sl)
}
}
func BenchmarkConcatStringByBytesBuffer(b *testing.B) {
for n : 0; n b.N; n {
concatStringByBytesBuffer(sl)
}
}
func BenchmarkConcatStringByBytesBufferWithInitSize(b *testing.B) {
for n : 0; n b.N; n {
concatStringByBytesBufferWithInitSize(sl)
}
}
运行该基准测试
$go test -bench. -benchmem ./string_concat_benchmark_test.go
goos: darwin
goarch: amd64
BenchmarkConcatStringByOperator-8 11744653 89.1 ns/op 80 B/op 2 allocs/op
BenchmarkConcatStringBySprintf-8 2792876 420 ns/op 176 B/op 8 allocs/op
BenchmarkConcatStringByJoin-8 22923051 49.1 ns/op 48 B/op 1 allocs/op
BenchmarkConcatStringByStringsBuilder-8 11347185 96.6 ns/op 112 B/op 3 allocs/op
BenchmarkConcatStringByStringsBuilderWithInitSize-8 26315769 42.3 ns/op 64 B/op 1 allocs/op
BenchmarkConcatStringByBytesBuffer-8 14265033 82.6 ns/op 112 B/op 2 allocs/op
BenchmarkConcatStringByBytesBufferWithInitSize-8 24777525 48.1 ns/op 48 B/op 1 allocs/op
PASS
ok command-line-arguments 8.816s从基准测试的输出结果的第三列即每操作耗时的数值来看
● 做了预初始化的strings.Builder连接构建字符串效率最高
● 带有预初始化的bytes.Buffer和strings.Join这两种方法效率十分接近分列二三位
● 未做预初始化的strings.Builder、bytes.Buffer和操作符连接在第三档次
● fmt.Sprintf性能最差排在末尾。
由此可以得出一些结论
● 在能预估出最终字符串长度的情况下使用预初始化的strings.Builder连接构建字符串效率最高
● strings.Join连接构建字符串的平均性能最稳定如果输入的多个字符串是以[]string承载的那么strings.Join也是不错的选择
● 使用操作符连接的方式最直观、最自然在编译器知晓欲连接的字符串个数的情况下使用此种方式可以得到编译器的优化处理
● fmt.Sprintf虽然效率不高但也不是一无是处如果是由多种不同类型变量来构建特定格式的字符串那么这种方式还是最适合的。
转换
在前面的例子中我们看到了string到[]rune以及string到[]byte的转换这两个转 换也是可逆的也就是说string和[]rune、[]byte可以双向转换。下面就是从[]rune或 []byte反向转换为string的例子
func main() {rs : []rune{0x4E2D,0x56FD,0x6B22,0x8FCE,0x60A8,}s : string(rs)fmt.Println(s)sl : []byte{0xE4, 0xB8, 0xAD,0xE5, 0x9B, 0xBD,0xE6, 0xAC, 0xA2,0xE8, 0xBF, 0x8E,0xE6, 0x82, 0xA8,}s string(sl)fmt.Println(s)
}$go run string_slice_to_string.go
中国欢迎您
中国欢迎您无论是string转slice还是slice转string转换都是要付出代价的这些代价的根源 在于string是不可变的运行时要为转换后的类型分配新内存。我们以byte slice与 string相互转换为例看看转换过程的内存分配情况
func byteSliceToString() {
sl : []byte{
0xE4, 0xB8, 0xAD,
0xE5, 0x9B, 0xBD,
0xE6, 0xAC, 0xA2,
0xE8, 0xBF, 0x8E,
0xE6, 0x82, 0xA8,
0xEF, 0xBC, 0x8C,
0xE5, 0x8C, 0x97,
0xE4, 0xBA, 0xAC,
0xE6, 0xAC, 0xA2,
0xE8, 0xBF, 0x8E,0xE6, 0x82, 0xA8,
}
_ string(sl)
}
func stringToByteSlice() {
s : 中国欢迎您北京欢迎您
_ []byte(s)
}
func main() {
fmt.Println(testing.AllocsPerRun(1, byteSliceToString))
fmt.Println(testing.AllocsPerRun(1, stringToByteSlice))
}运行这个例子
$go run string_mallocs_in_convert.go
1
1我们看到针对“中国欢迎您北京欢迎您”这个长度的字符串在string与byte slice互转的过程中都要有一次内存分配操作。
在Go运行时层面字符串与rune slice、byte slice相互转换对应的函数如下
slicebytetostring: []byte - string
slicerunetostring: []rune - string
stringtoslicebyte: string - []byte
stringtoslicerune: string - []rune以byte slice为例看看slicebytetostring和stringtoslicebyte的实现
const tmpStringBufSize 32
type tmpBuf [tmpStringBufSize]byte
func stringtoslicebyte(buf *tmpBuf, s string) []byte {
var b []byte
if buf ! nil len(s) len(buf) {
*buf tmpBuf{}
b buf[:len(s)]
} else {
b rawbyteslice(len(s))
}
copy(b, s)
return b
}func slicebytetostring(buf *tmpBuf, b []byte) (str string) {l : len(b)if l 0 {return }// 此处省略一些代码if l 1 {stringStructOf(str).str unsafe.Pointer(staticbytes[b[0]])stringStructOf(str).len 1return}var p unsafe.Pointerif buf ! nil len(b) len(buf) {p unsafe.Pointer(buf)} else {p mallocgc(uintptr(len(b)), nil, false)}stringStructOf(str).str pstringStructOf(str).len len(b)memmove(p, (*(*slice)(unsafe.Pointer(b))).array, uintptr(len(b)))return
}想要更高效地进行转换唯一的方法就是减少甚至避免额外的内存分配操作。我们看 到运行时实现转换的函数中已经加入了一些避免每种情况都要分配新内存操作的优化如 tmpBuf的复用。 slice类型是不可比较的而string类型是可比较的因此在日常Go编码中我们会经 常遇到将slice临时转换为string的情况。Go编译器为这样的场景提供了优化。在运行时中 有一个名为slicebytetostringtmp的函数就是协助实现这一优化的
func slicebytetostringtmp(b []byte) string {if raceenabled len(b) 0 {racereadrangepc(unsafe.Pointer(b[0]),uintptr(len(b)),getcallerpc(),funcPC(slicebytetostringtmp))}if msanenabled len(b) 0 {msanread(unsafe.Pointer(b[0]), uintptr(len(b)))}return *(*string)(unsafe.Pointer(b))
}该函数的“秘诀”就在于不为string新开辟一块内存而是直接使用slice的底层存 储。当然使用这个函数的前提是在原slice被修改后这个string不能再被使用了。因此 这样的优化是针对以下几个特定场景的。
1string(b)用在map类型的key中
b : []byte{k, e, y}
m : make(map[string]string)
m[string(b)] value
m[[3]string{string(b), key1, key2}] value1
2string(b)用在字符串连接语句中
b : []byte{t, o, n, y}
s : hello string(b) !
3string(b)用在字符串比较中
s : tom
b : []byte{t, o, n, y}
if s string(b) {
...
}Go编译器对用在for-range循环中的string到[]byte的转换也有优化处理它不会为 []byte进行额外的内存分配而是直接使用string的底层数据。
看下面的例子
func convert() {
s : 中国欢迎您北京欢迎您
sl : []byte(s)
for _, v : range sl {
_ v
}
}
func convertWithOptimize() {
s : 中国欢迎您北京欢迎您
for _, v : range []byte(s) {
_ v
}
}
func main() {
fmt.Println(testing.AllocsPerRun(1, convert))
fmt.Println(testing.AllocsPerRun(1, convertWithOptimize))
}运行这个例子程序
$go run string_for_range_covert_optimize.go
1
0从结果我们看到convertWithOptimize函数将string到[]byte的转换放在for-range 循环中Go编译器对其进行了优化节省了一次内存分配操作。