Home >Backend Development >Golang >Detailed explanation of the comparison between golang string and []byte
The following tutorial column will introduce to you the comparison and difference between golang string and []byte. I hope it will be helpful to friends in need!
Comparison between golang string and []byte
Why string and []byte type conversion requires certain cost?
Why does the built-in function copy have a special case?copy(dst []byte, src string) int?
string and []byte, the bottom layer is an array , but why []byte is more flexible than string and has higher splicing performance (dynamic string splicing performance comparison)? I read the source code today and explored it.
All the following opinions are my humble opinions. If you have different suggestions or additions, please email me aboutme
What is string?
What is a string? Explanation of the standard library builtin:
type string string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. A string may be empty, but not nil. Values of string type are immutable.
Simply put, a string is a collection of 8-bit bytes, usually but not necessarily representing UTF-8 encoded text. Strings can be empty, but not nil. And the value of the string cannot be changed. Different language strings have different implementations. In the source code of go,
src/runtime/string.go
type stringStruct struct { str unsafe.Pointer len int}
<pre style='font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 1rem; display: block; padding: 9.5px; margin: 0 0 10px; color: rgba(51, 51, 51, 1); background-color: rgba(245, 245, 245, 1); border: 1px solid rgba(204, 204, 204, 1); border-radius: 4px; font-style: normal; font-weight: normal; text-indent: 0'>func gostringnocopy(str *byte) string {
ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}
s := *(*string)(unsafe.Pointer(&ss)) return s
}</pre>
Haha, it is actually a byte array, and it should be noted that string is actually a struct. What is []byte?
First of all, in go, byte is an alias for uint8. The slice structure is defined in the source code of go
src/runtime/slice.go:
type slice struct { array unsafe.Pointer len int cap int}
array是数组的指针,len表示长度,cap表示容量。除了cap,其他看起来和string的结构很像。
但其实他们差别真的很大。
区别
字符串的值是不能改变
在前面说到了字符串的值是不能改变的,这句话其实不完整,应该说字符串的值不能被更改,但可以被替换。 还是以string的结构体来解释吧,所有的string在底层都是这样的一个结构体stringStruct{str: str_point, len: str_len}
,string结构体的str指针指向的是一个字符常量的地址, 这个地址里面的内容是不可以被改变的,因为它是只读的,但是这个指针可以指向不同的地址,我们来对比一下string、[]byte类型重新赋值的区别:
s := "A1" // 分配存储"A1"的内存空间,s结构体里的str指针指向这快内存 s = "A2" // 重新给"A2"的分配内存空间,s结构体里的str指针指向这快内存
其实[]byte和string的差别是更改变量的时候array的内容可以被更改。
s := []byte{1} // 分配存储1数组的内存空间,s结构体的array指针指向这个数组。s = []byte{2} // 将array的内容改为2
因为string的指针指向的内容是不可以更改的,所以每更改一次字符串,就得重新分配一次内存,之前分配空间的还得由gc回收,这是导致string操作低效的根本原因。
string和[]byte的相互转换
将string转为[]byte,语法[]byte(string)
源码如下:
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 rawstring(size int) (s string, b []byte) { p := mallocgc(uintptr(size), nil, false) stringStructOf(&s).str = p stringStructOf(&s).len = size *(*slice)(unsafe.Pointer(&b)) = slice{p, size, size} return}
可以看到b是新分配的,然后再将s复制给b,至于为啥copy函数可以直接把string复制给[]byte,那是因为go源码单独实现了一个slicestringcopy
函数来实现,具体可以看src/runtime/slice.go
。
将[]byte转为string,语法string([]byte)
源码如下:
func slicebytetostring(buf *tmpBuf, b []byte) string { l := len(b) if l == 0 { // Turns out to be a relatively common case. // Consider that you want to parse out data between parens in "foo()bar", // you find the indices and convert the subslice to string. return "" } if raceenabled && l > 0 { racereadrangepc(unsafe.Pointer(&b[0]), uintptr(l), getcallerpc(unsafe.Pointer(&buf)), funcPC(slicebytetostring)) } if msanenabled && l > 0 { msanread(unsafe.Pointer(&b[0]), uintptr(l)) } s, c := rawstringtmp(buf, l) copy(c, b) return s }func rawstringtmp(buf *tmpBuf, l int) (s string, b []byte) { if buf != nil && l <= len(buf) { b = buf[:l] s = slicebytetostringtmp(b) } else { s, b = rawstring(l) } return}
依然可以看到s是新分配的,然后再将b复制给s。
正因为string和[]byte相互转换都会有新的内存分配,才导致其代价不小,但读者千万不要误会,对于现在的机器来说这些代价其实不值一提。 但如果想要频繁string和[]byte相互转换(仅假设),又不会有新的内存分配,能有办法吗?答案是有的。
package string_slicebyte_testimport ( "log" "reflect" "testing" "unsafe")func stringtoslicebyte(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)) }func slicebytetostring(b []byte) string { bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) sh := reflect.StringHeader{ Data: bh.Data, Len: bh.Len, } return *(*string)(unsafe.Pointer(&sh)) }func TestStringSliceByte(t *testing.T) { s1 := "abc" b1 := []byte("def") copy(b1, s1) log.Println(s1, b1) s := "hello" b2 := stringtoslicebyte(s) log.Println(b2) // b2[0] = byte(99) unexpected fault address b3 := []byte("test") s3 := slicebytetostring(b3) log.Println(s3) }
答案虽然有,但强烈推荐不要使用这种方法来转换类型,因为如果通过stringtoslicebyte将string转为[]byte的时候,共用的时同一块内存,原先的string内存区域是只读的,一但更改将会导致整个进程down掉,而且这个错误是runtime没法恢复的。
如何取舍?
既然string就是一系列字节,而[]byte也可以表达一系列字节,那么实际运用中应当如何取舍?
最后脱离场景谈性能都是耍流氓,需要根据实际场景来抉择。
更多golang相关技术文章,请访问go语言栏目!
The above is the detailed content of Detailed explanation of the comparison between golang string and []byte. For more information, please follow other related articles on the PHP Chinese website!