Golang---基本型別(string)
摘要:由於在實習過程中,做的專案都是基於 Golang 語言,所以在面試時,面試官也一定會理所當然的問 Golang, 所以在最近一段時間,主要學習這門語言的基礎知識,以及常出的面試題。
簡單介紹
字串雖然在 Go 語言中是基本型別 string, 但是它實際上是由字元組成的陣列,類似於 C 語言中的 char [] ,作為陣列會佔用一片連續的記憶體空間。Go 語言中的字串其實只是一個只讀的位元組陣列,不支援直接修改 string 型別變數的記憶體空間,比如下面程式碼就是不支援的:
package main import ( "fmt" ) func main() { s :err-example1= "hello" s[0] = 'A' fmt.Println(s) } //.\main.go:9:7: cannot assign to s[0]
如果我們想修改字串,我們可以將這段記憶體拷貝到堆或者棧上,將遍歷的型別轉換為 []byte 之後就可以進行,修改後通過型別轉換就可以變回 string, 對原變數重新賦值即可。
package main import ( "fmt" ) func main() { s := "hello" sByte := []byte(s) sByte[0] = 'A' //重新賦值 sright-example1= string(sByte) fmt.Println(s) } //Aello
資料結構
字串在 Go 語言中的介面其實非常簡單,每一個字串在執行時都會使用如下的 StringHeader 結構體表示,其實在”執行時“內部,有一個私有的結構 stringHeader, 它有著完全相同的結構,只是用於儲存資料的 Data 欄位使用了 unsafe.Pointer 型別:
// StringHeader is the runtime representation of a string. // It cannot be used safely orstring-structportably 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 }
宣告方式
使用雙引號
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/