golang实现不分配额外内存[]byte和string转换
普通的强转
一般在进行[]byte 和string之间进行转换时,通过进行强转实现。这样进行强转的话,都会在额外的拷贝,从而造成额外的内存分配。
str := "test"
// 转换为[]byte
bytes := []byte(str)
// 转换为str
str1 := string(bytes)
优化后
// 通过底层数据转换
package strbytesconv
import (
"reflect"
"unsafe"
)
// StringToBytes 实现string 转换成 []byte, 不用额外的内存分配
func StringToBytes(str string) (bytes []byte) {
ss := *(*reflect.StringHeader)(unsafe.Pointer(&str))
bs := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))
bs.Data = ss.Data
bs.Len = ss.Len
bs.Cap = ss.Len
return bytes
}
// BytesToString 实现 []byte 转换成 string, 不需要额外的内存分配
func BytesToString(bytes []byte) string {
return *(*string)(unsafe.Pointer(&bytes))
}
- StringHeader 是go中string 在底层的定义
type StringHeader struct {
Data uintptr
Len int
}
- SliceHeader 是go中切片 在底层的定义
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
上诉的优化方法通过将unsafe.Pointer获取地址,实现 SliceHeader 和 SliceHeader 之间的转换。
测试
准确率测试-通过
func Test_StringToBytes(t *testing.T) {
type args struct {
str string
}
tests := []struct {
name string
args args
wantBytes []byte
}{
// TODO: Add test cases.
{
name: "testStrToBytes",
args: args{
str: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
},
wantBytes: []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotBytes := StringToBytes(tt.args.str)
if !reflect.DeepEqual(gotBytes, tt.wantBytes) {
t.Errorf("StringToBytes() = %v, want %v", gotBytes, tt.wantBytes)
}
t.Logf("gotBytes Pointer %p, tt.args.str Pointer %p", &gotBytes, &tt.args.str)
})
}
}
func Test_BytesToString(t *testing.T) {
type args struct {
bytes []byte
}
tests := []struct {
name string
args args
want string
}{
// TODO: Add test cases.
{
name: "testBytesToStr",
args: args{
bytes: []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"),
},
want: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := BytesToString(tt.args.bytes)
if got != tt.want {
t.Errorf("BytesToString() = %v, want %v", got, tt.want)
}
t.Logf("gotStr Pointer %p, tt.args.bytes Pointer %p", &got, &tt.args.bytes)
})
}
}
基准测试
var testStr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
var testBytes = []byte(testStr)
func getBytesToString(bytes []byte) string {
return string(bytes)
}
func getStringToBytes(str string) []byte {
return []byte(str)
}
func Benchmark_StringToBytes(b *testing.B) {
for i := 0; i < b.N; i++ {
StringToBytes(testStr)
}
}
func Benchmark_getStringToBytes(b *testing.B) {
for i := 0; i < b.N; i++ {
getStringToBytes(testStr)
}
}
func Benchmark_BytesToString(b *testing.B) {
for i := 0; i < b.N; i++ {
BytesToString(testBytes)
}
}
func Benchmark_getBytesToString(b *testing.B) {
for i := 0; i < b.N; i++ {
getBytesToString(testBytes)
}
}
结果
执行:
go test -v -run=none -bench=. -benchmem=true
Benchmark_StringToBytes-8 1000000000 0.477 ns/op 0 B/op 0 allocs/op
Benchmark_getStringToBytes-8 20491503 58.0 ns/op 64 B/op 1 allocs/op
Benchmark_BytesToString-8 1000000000 0.473 ns/op 0 B/op 0 allocs/op
Benchmark_getBytesToString-8 22655529 49.1 ns/op 64 B/op 1 allocs/op
可以看出,有明显的速度提升