golang string 和 []byte的關系

首先,string內部就是一個byte數組
結構如下

type stringStruct struct {
    str unsafe.Pointer
    len int
}

可以看到str其實是個指針,指向某個數組的首地址,另一個字段是len長度。那到這個數組是什么呢? 在實例化這個stringStruct的時候:

func gostringnocopy(str *byte) string {
    ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}
    s := *(*string)(unsafe.Pointer(&ss))
    return s
}

[]byte 是個slice數據,byte是uint8,而slice結構在go的源碼中src/runtime/slice.go定義:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

看著和上面的string結構很像,但其實差別很大

差別在哪?

首先,字符串的值可以被替換但不能被更改。 以string的結構體來解釋,所有的string在底層都是這樣的一個結構體

stringStruct{
    str: str_point, 
    len: str_len
}

str指針指向的是一個字符常量的地址, 這個地址里面的內容是不可以被改變的,因為它是只讀的,但是這個指針可以指向不同的地址。
如下:123 是字符常量,而常量是不允許修改的,str 指向 字符常量的內存地址。456也是字符常量,456賦給str時,是重新指向了 456的內存地址。

str := "123"
str = "456"

byte數據則不同,[]byte{1}是slice,其內容是可以被更改的。

str := []byte{1}
str = []byte{2}

這就是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
}

這里新申請了內存空間 var b []byte 。是邏輯是先判斷buf是否為空,以及長度是否夠用,不夠用通過rawstring 擴容
然后通過copy將 s 復制給了 b
這里就有個新的內存空間使用。

將[]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類型是我們編程時最常使用到的數據結構。本文將探討兩者之間的轉換方式,通過分析它們之間...
    機器鈴砍菜刀s閱讀 4,006評論 0 0
  • slice 切片的原理 切? ( slice ) 是Go中?種?較特殊的數據結構,這種數據結構更便于使?和管理數據...
    強某某閱讀 1,114評論 0 1
  • 1.安裝 https://studygolang.com/dl 2.使用vscode編輯器安裝go插件 3.go語...
    go含羞草閱讀 1,567評論 0 6
  • 主函數:程序有且只有一個主函數package main //導入主函數的包func main() {} 打印的模...
    hellomyshadow閱讀 180評論 0 0
  • 在講解String之前,我們先了解一下Java的內存結構。 一、Java內存模型 按照官方的說法:Java 虛擬機...
    哦00閱讀 257評論 0 0