golang实现不分配额外内存[]byte和string转换

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

可以看出,有明显的速度提升