string和[]byte转换会发生内存拷贝吗

https://www.cnblogs.com/shuiyuejiangnan/p/9707066.html

https://blog.csdn.net/slphahaha/article/details/109405685

cuishuangtodo

前文预览:

golang之string类型变量操作的原子性

Go生僻字符串操作

大佬们,小白问个问题, 用string() 转换一个 []byte 时,内存会重新分配吗?


浅拷贝和深拷贝


浅拷贝(也称为位拷贝)就是拷贝指向对象的指针,即拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间 (改其中一个会影响到另外一个)。浅拷贝只是一种简单的拷贝,让几个对象共用一个内存。 浅拷贝可能会有数据安全方面的隐患

深拷贝则是另辟了一个完全独立的内存空间,源对象与拷贝对象互相独立,改其中一个不会影响
另外一个。 但需为新的变量 重新分配一块内存


string(byteSli)操作,会为新生成的str,重新分配(一块)内存吗


Go源码里大多都是[]byte而不是string:想改变其中几个字符的时候直接通过下标修改[]byte里面的内容,在需要string的时候通过string([]byte)得到字符串。这可以提高效率,还不会产生太多的子字符串浪费内存,导致GC任务加重

但如下细节需注意:

string(字节数组)方式转换生成的字符串,是重新开辟了一块内存(如str := string(byteSli))。在转换后,再去修改原来byteSli的某几个字符(如byteSli[1] = 'x'),str将不会跟随改变 (str[1]还是原来的字符,而不是’x’)

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
60
61
62
63
64
65
66
67
68
69
70
71
72
package main

import (
"fmt"
"github.com/davecgh/go-spew/spew"
"unsafe"
)

//仿造string的底层结构
type FakeString struct {
ptr *byte
len int
}

//仿造[]byte的底层结构
type FakeByteSlice struct {
ptr *byte
len int
cap int
}

func main() {
var tmp FakeString
var byteSli = []byte("hai")

str := string(byteSli)
//str是通过强制转换[]byte而来,str底层的dataPtr与[]byte底层的ptr指向不同的内存地址,

spew.Dump("byteSli", &byteSli)

fmt.Println("------")

spew.Dump("str", &str)

fmt.Println("------")

//下面是证明:

//利用unsafe包,强制把byteSli的起始地址看成FakeByteSlice的起始地址,并赋值给tmpByte
tmpByte := *((*FakeByteSlice)(unsafe.Pointer(&byteSli)))
//打印byteSli的一些信息:
fmt.Printf("size of bytes :%d, len(byteSli):%d, cap(byteSli):%d,&byteSli[0]:%p\n",
unsafe.Sizeof(byteSli), len(byteSli), cap(byteSli), &byteSli[0])

//通过tmpByte查看byteSli里面的东西:
fmt.Printf("tmpByte-> len:%d, cap:%d, ptr:%p\n",
tmpByte.len, tmpByte.cap, tmpByte.ptr)
/*输出:
size of bytes :24, len(byteSli):3, cap(byteSli):3,&byteSli[0]:0x140000a2160
tmpByte-> len:3, cap:3, ptr:0x140000a2160
*/

fmt.Println("------")

//强制把str的起始地址看成FakeString 的起始地址,并赋值给tmp 变量
tmp = *((*FakeString)(unsafe.Pointer(&str)))
//通过tmp查看str里面的东西
fmt.Printf("len of string :%d, addr of data:%p, len of str:%d\n",
len(str), tmp.ptr, tmp.len)
/*输出:
len of string :3, addr of data:0x140000a2163, len of str:3
可见 tmp.ptr != tmpByte.ptr
*/

//进一步的证明
byteSli[1] = 'o' //修改byte[1]
fmt.Printf("str:%s, data addr: %x \n",
str, *((*uintptr)(unsafe.Pointer(&str))))
//输出:str:hai, data addr: 140000a2163
//修改了byte[1]之后,str还是原来的hai。
}


输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(string) (len=7) "byteSli"
(*[]uint8)(0x140000ae048)((len=3 cap=3) {
00000000 68 61 69 |hai|
})
------
(string) (len=3) "str"
(*string)(0x1400009e120)((len=3) "hai")
------
size of bytes :24, len(byteSli):3, cap(byteSli):3,&byteSli[0]:0x140000a2160
tmpByte-> len:3, cap:3, ptr:0x140000a2160
------
len of string :3, addr of data:0x140000a2163, len of str:3
str:hai, data addr: 140000a2163


内存地址不同,给str新分配(一块)内存,可以写benchmark验证一下


byte切片转字符串,有没有可以零拷贝的方式?


当然有,使用unsafe的方式

不同写法的性能差异之byte切片转string


一些底层实现和细节


参考:

面试官:string和[]byte转换原理知道吗?会发生内存拷贝吗?

Golang中[]byte与string转换

go string与[]byte转换以及性能分析

go中string是如何实现的