1. 程式人生 > 實用技巧 >Golang---基本型別(string)

Golang---基本型別(string)

摘要:由於在實習過程中,做的專案都是基於 Golang 語言,所以在面試時,面試官也一定會理所當然的問 Golang, 所以在最近一段時間,主要學習這門語言的基礎知識,以及常出的面試題。

簡單介紹

字串雖然在 Go 語言中是基本型別 string, 但是它實際上是由字元組成的陣列,類似於 C 語言中的 char [] ,作為陣列會佔用一片連續的記憶體空間。Go 語言中的字串其實只是一個只讀的位元組陣列,不支援直接修改 string 型別變數的記憶體空間,比如下面程式碼就是不支援的:

package main

import (
    "fmt"
)

func main() {
    s :
= "hello" s[0] = 'A' fmt.Println(s) } //.\main.go:9:7: cannot assign to s[0]
err-example1

如果我們想修改字串,我們可以將這段記憶體拷貝到堆或者棧上,將遍歷的型別轉換為 []byte 之後就可以進行,修改後通過型別轉換就可以變回 string, 對原變數重新賦值即可。

package main

import (
    "fmt"
)

func main() {
    s := "hello"
    sByte := []byte(s)
    sByte[0] = 'A'

    //重新賦值
    s 
= string(sByte) fmt.Println(s) } //Aello
right-example1

資料結構

字串在 Go 語言中的介面其實非常簡單,每一個字串在執行時都會使用如下的 StringHeader 結構體表示,其實在”執行時“內部,有一個私有的結構 stringHeader, 它有著完全相同的結構,只是用於儲存資料的 Data 欄位使用了 unsafe.Pointer 型別:

// StringHeader is the runtime representation of a string.
// It cannot be used safely or
portably and its representation may // change in a later release. // Moreover, the Data field is not sufficient to guarantee the data // it references will not be garbage collected, so programs must keep // a separate, correctly typed pointer to the underlying data. type StringHeader struct { Data uintptr Len int } // stringHeader is a safe version of StringHeader used within this package. type stringHeader struct { Data unsafe.Pointer Len int }
string-struct

宣告方式

使用雙引號

s := "hello world"

使用反引號

s := `hello world`

使用雙引號可其它語言沒有什麼大的區別,如果字串內部出現雙引號,要使用 \ 進行轉義;但使用反引號則不需要,方便進行更加複雜的資料型別,比如 Json:

s := `{"name": "sween", "age": 18}`

注:上面兩種格式的解析函式分別為cmd/compile/internal/syntax.scanner.stdString

cmd/compile/internal/syntax.scanner.rawString

型別轉換

在我們使用 Go 語言解析和序列化 Json 等資料格式時,經常需要將資料在 string 和 []byte 之間進行轉換,型別轉換的開銷其實並沒有想象中的那麼小。

[]byte 到 string 的轉換

runtime.slicebytetostring 這個函式中進行轉換的處理,我們看下原始碼:

// slicebytetostring converts a byte slice to a string.
// It is inserted by the compiler into generated code.
// ptr is a pointer to the first element of the slice;
// n is the length of the slice.
// Buf is a fixed-size buffer for the result,
// it is not nil if the result does not escape.
func slicebytetostring(buf *tmpBuf, ptr *byte, n int) (str string) {
    if n == 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 n == 1 {
        p := unsafe.Pointer(&staticuint64s[*ptr])
        if sys.BigEndian {
            p = add(p, 7)
        }
        stringStructOf(&str).str = p
        stringStructOf(&str).len = 1
        return
    }

    var p unsafe.Pointer
    if buf != nil && n <= len(buf) {
        p = unsafe.Pointer(buf)
    } else {
                //step1: 分配記憶體空間
        p = mallocgc(uintptr(n), nil, false)
    }
    stringStructOf(&str).str = p
    stringStructOf(&str).len = n
        //step2:執行記憶體拷貝操作
    memmove(p, unsafe.Pointer(ptr), uintptr(n))
    return
}
[]byte 轉 string 原始碼

string 到 []byte 的轉換

runtime.stringtoslicebyte 這個函式中進行轉換的處理,我們看下原始碼:

func stringtoslicebyte(buf *tmpBuf, s string) []byte {
    var b []byte
    if buf != nil && len(s) <= len(buf) {
                //step1: 如果緩衝區夠用,直接用
        *buf = tmpBuf{}
        b = buf[:len(s)]
    } else {
                //step2: 如果緩衝區不夠用,重新分配一個
        b = rawbyteslice(len(s))
    }
        //step3: 執行記憶體拷貝操作
    copy(b, s)
    return b
}

// rawbyteslice allocates a new byte slice. The byte slice is not zeroed.
func rawbyteslice(size int) (b []byte) {
    cap := roundupsize(uintptr(size))
    p := mallocgc(cap, nil, false)
    if cap != uintptr(size) {
        memclrNoHeapPointers(add(p, uintptr(size)), cap-uintptr(size))
    }

    *(*slice)(unsafe.Pointer(&b)) = slice{p, size, int(cap)}
    return
}
string 轉 []byte 原始碼

總結

字串和 []byte 中的內容雖然一樣,但是字串的內容是隻讀的,我們不能通過下標或者其它形式改變其中的資料,而 []byte 中的內容是可讀寫的,無論哪種型別轉換到另一種型別都需要對其中的內容進行拷貝,而記憶體拷貝的效能損耗會隨著字串和 []byte 長度的增長而增長。所以在做型別轉換時候一定要注意效能的損耗。

參考資料:

https://draveness.me/golang/docs/part2-foundation/ch03-datastructure/golang-string/