golang 中 []byte 和string如何转换最快?

在 golang 中,我们经常需要对 []byte 和 string 进行转换,比如读写文件、处理网络数据、编码解码等场景。通常情况下,这不会成为系统的性能瓶颈,但是在某些极致情况下,也可能成为拖慢性能的关键因素。那么,如何选择合适的转换方式,以达到最快的速度呢?

一、标准转换

标准转换是最简单也最常用的转换方式,就是使用 []byte(s) 和 string(b) 这样的语法来进行转换,其中 s 是一个字符串,b 是一个字节切片。这种方式的优点是语法简洁,易于理解,而且可以保证类型安全和数据不变性。但是,这种方式的缺点是可能会涉及内存分配和复制操作,因为字符串在 golang 中是不可变的,所以每次转换都会创建一个新的数据。

package main

import (
	"testing"
)

// 测试标准转换 string() 性能
func Benchmark_NormalBytes2String(b *testing.B) {
	x := []byte("Hello Gopher! Hello Gopher! Hello Gopher!")
	for i := 0; i < b.N; i++ {
		_ = string(x)
	}
}

// 测试标准转换 []byte 性能
func Benchmark_NormalString2Bytes(b *testing.B) {
	x := "Hello Gopher! Hello Gopher! Hello Gopher!"
	for i := 0; i < b.N; i++ {
		_ = []byte(x)
	}
}

运行 go test -bench="." -benchmem ,得到以下结果:

goos: darwin
goarch: amd64
pkg: example/standard
Benchmark_NormalBytes2String-8          83988709                13.60 ns/op           48 B/op          1 allocs/op
Benchmark_NormalString2Bytes-8          73114815                16.39 ns/op           48 B/op          1 allocs/op

PASS
ok  	example/standard	3.170s

可以看到,标准转换每次操作都会分配 48 字节的内存,并且耗时在 15 纳秒左右。

二、强制转换

强制转换是一种使用 unsafe 和 reflect 包来直接操作底层的数据结构的转换方式,这种方式可以避免内存分配和复制操作,从而提高性能。但是,这种方式的缺点是可能会破坏类型安全和数据不变性,因为它会直接修改原始数据的指针和长度信息,所以需要谨慎使用,并且遵守一些规则,比如不要修改原始数据、不要超出原始数据的范围、不要让原始数据被垃圾回收等。

我们可以通过以下代码来实现强制转换,并测试它的性能:

package main

import (
	"bytes"
	"reflect"
	"testing"
	"unsafe"
)

// 强制转换 string 到 []byte
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))
}

// 强制转换 []byte 到 string
func Bytes2String(b []byte) string {
	return *(*string)(unsafe.Pointer(&b))
}

// 测试强制转换 string() 性能
func Benchmark_Byte2String(b *testing.B) {
	x := []byte("Hello Gopher! Hello Gopher! Hello Gopher!")
	for i := 0; i < b.N; i++ {
		_ = Bytes2String(x)
	}
}

// 测试强制转换 []byte 性能
func Benchmark_String2Bytes(b *testing.B) {
	x := "Hello Gopher! Hello Gopher! Hello Gopher!"
	for i := 0; i < b.N; i++ {
		_ = String2Bytes(x)
	}
}

运行 go test -bench="." -benchmem ,得到以下结果:

goos: darwin
goarch: amd64
pkg: example/force
Benchmark_Byte2String-8                 1000000000               0.3156 ns/op          0 B/op          0 allocs/op
Benchmark_String2Bytes-8                1000000000               0.3137 ns/op          0 B/op          0 allocs/op

PASS
ok  	example/force	1.586s

可以看到,强制转换每次操作都不会分配内存,并且耗时在 1 纳秒以下,性能明显优于标准转换。

三、拼接转换

拼接转换是一种使用 strings.Builder 或 bytes.Buffer 来拼接字符串或字节切片的转换方式。通常只有一个字符串或者字节切片,我们并不会选择这种方式来进行转换,因为这会增加操作步骤,性能不会太高。但是如果我们有很多字符串或者字节切片需要拼接并转换,这或许是一种方式。这种方式可以减少内存分配的次数,提高性能。

我们可以通过以下代码来实现拼接转换,并测试它的性能:

package main

import (
	"bytes"
	"strings"
	"testing"
)

// 拼接转换 string 到 []byte
func StringJoinBytes(s string) []byte {
	var b bytes.Buffer
	b.WriteString(s)
	return b.Bytes()
}

// 拼接转换 []byte 到 string
func BytesJoinString(b []byte) string {
	var s strings.Builder
	s.Write(b)
	return s.String()
}

// 测试拼接转换 string() 性能
func Benchmark_BytesJoinString(b *testing.B) {
	x := []byte("Hello Gopher! Hello Gopher! Hello Gopher!")
	for i := 0; i < b.N; i++ {
		_ = BytesJoinString(x)
	}
}

// 测试拼接转换 []byte 性能
func Benchmark_StringJoinBytes(b *testing.B) {
	x := "Hello Gopher! Hello Gopher! Hello Gopher!"
	for i := 0; i < b.N; i++ {
		_ = StringJoinBytes(x)
	}
}

运行 go test -bench="." -benchmem ,得到以下结果:

goos: darwin
goarch: amd64
pkg: example/join
Benchmark_BytesJoinString-8             66973782                18.25 ns/op           48 B/op          1 allocs/op
Benchmark_StringJoinBytes-8             55316959                21.21 ns/op           64 B/op          1 allocs/op

PASS
ok  	example/join	2.112s

可以看到,拼接转换每次操作只会分配一次内存,并且耗时在 10 纳秒左右,性能比标准转换还要差。

四、总结

可以看到,我们在 golang 中可以使用标准转换、强制转换或拼接转换来进行 []byte 和 string 的转换。

  1. 标准转换是最常用的方式,语法简洁易懂,但可能涉及内存分配和复制操作。
  2. 强制转换可以避免内存分配和复制操作,性能更高,但需要注意类型安全和数据不变性。
  3. 拼接转换适用于需要拼接多个字符串或字节切片的场景,可以减少内存分配次数,提高性能。根据具体的使用场景和需求,选择合适的转换方式可以达到最快的速度。
#golang##字符串处理##go开发#
全部评论

相关推荐

2 10 评论
分享
牛客网
牛客企业服务