《快學 Go 語言》第 7 課 —— 誘人的烤串
字串通常有兩種設計,一種是「字元」串,一種是「位元組」串。「字元」串中的每個字都是定長的,而「位元組」串中每個字是不定長的。Go 語言裡的字串是「位元組」串,英文字元佔用 1 個位元組,非英文字元佔多個位元組。這意味著無法透過位置來快速定位出一個完整的字元來,而必須透過遍歷的方式來逐個獲取單個字元。
我們所說的字元通常是指 unicode 字元,你可以認為所有的英文和漢字在 unicode 字符集中都有一個唯一的整數編號,一個 unicode 通常用 4 個位元組來表示,對應的 Go 語言中的字元 rune 佔 4 個位元組。在 Go 語言的原始碼中可以找到下面這行程式碼,rune 型別是一個衍生型別,它在記憶體裡面使用 int32 型別的 4 個位元組儲存。
type rune int32
使用「字元」串來表示字串勢必會浪費空間,因為所有的英文字元本來只需要 1 個位元組來表示,用 rune 字元來表示的話那麼剩餘的 3 個位元組都是零。但是「字元」串有一個好處,那就是可以快速定位。
為了進一步方便讀者理解位元組 byte 和 字元 rune 的關係,我畫了下面這張圖
其中 codepoint 是每個「字」的其實偏移量。Go 語言的字串採用 utf8 編碼,中文漢字通常需要佔用 3 個位元組,英文只需要 1 個位元組。len() 函式得到的是位元組的數量,透過下標來訪問字串得到的是「位元組」。
按位元組遍歷
字串可以透過下標來訪問內部位元組陣列具體位置上的位元組,位元組是 byte 型別
package main
import "fmt"
func main() {
var s = "嘻哈china"
for i:=0;i<len(s);i++ {
fmt.Printf("%x ", s[i])
}
}
-----------
e5 98 bb e5 93 88 63 68 69 6e 61
按字元 rune 遍歷
package main
import "fmt"
func main() {
var s = "嘻哈china"
for codepoint, runeValue := range s {
fmt.Printf("%d %d ", codepoint, int32(runeValue))
}
}
-----------
0 22075 3 21704 6 99 7 104 8 105 9 110 10 97
對字串進行 range 遍歷,每次迭代出兩個變數 codepoint 和 runeValue。codepoint 表示字元起始位置,runeValue 表示對應的 unicode 編碼(型別是 rune)。
位元組串的記憶體表示
如果字串僅僅是位元組陣列,那字串的長度資訊是怎麼得到呢?要是字串都是字面量的話,長度尚可以在編譯期計算出來,但是如果字串是執行時構造的,那長度又是如何得到的呢?
var s1 = "hello" // 靜態字面量
var s2 = ""
for i:=0;i<10;i++ {
s2 += s1 // 動態構造
}
fmt.Println(len(s1))
fmt.Println(len(s2))
為解釋這點,就必須瞭解字串的記憶體結構,它不僅僅是前面提到的那個位元組陣列,編譯器還為它分配了頭部欄位來儲存長度資訊和指向底層位元組陣列的指標,圖示如下,結構非常類似於切片,區別是頭部少了一個容量欄位。
當我們將一個字串變數賦值給另一個字串變數時,底層的位元組陣列是共享的,它只是淺複製了頭部欄位。
字串是隻讀的
你可以使用下標來讀取字串指定位置的位元組,但是你無法修改這個位置上的位元組內容。如果你嘗試使用下標賦值,編譯器在語法上直接拒絕你。
package main
func main() {
var s = "hello"
s[0] = 'H'
}
--------
./main.go:5:7: cannot assign to s[0]
切割切割
字串在記憶體形式上比較接近於切片,它也可以像切片一樣進行切割來獲取子串。子串和母串共享底層位元組陣列。
package main
import "fmt"
func main() {
var s1 = "hello world"
var s2 = s1[3:8]
fmt.Println(s2)
}
-------
lo wo
位元組切片和字串的相互轉換
在使用 Go 語言進行網路程式設計時,經常需要將來自網路的位元組流轉換成記憶體字串,同時也需要將記憶體字串轉換成網路位元組流。Go 語言直接內建了位元組切片和字串的相互轉換語法。
package main
import "fmt"
func main() {
var s1 = "hello world"
var b = []byte(s1) // 字串轉位元組切片
var s2 = string(b) // 位元組切片轉字串
fmt.Println(b)
fmt.Println(s2)
}
--------
[104 101 108 108 111 32 119 111 114 108 100]
hello world
從節省記憶體的角度出發,你可能會認為位元組切片和字串的底層位元組陣列是共享的。但是事實不是這樣的,底層位元組陣列會被複製。如果內容很大,那麼轉換操作是需要一定成本的。
那為什麼需要複製呢?因為位元組切片的底層陣列內容是可以修改的,而字串的底層位元組陣列是隻讀的,如果共享了,就會導致字串的只讀屬性不再成立。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31561269/viewspace-2221119/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 《快學 Go 語言》第 7 課 —— 冰糖葫蘆串Go
- 《快學 Go 語言》第 7 課 —— 字串Go字串
- 《快學 Go 語言》第 6 課 —— 字典Go
- 《快學 Go 語言》第 14 課 —— 反射Go反射
- 《快學 Go 語言》第 12 課 —— 通道Go
- 《快學 Go 語言》第 9 課 —— 介面Go
- 《快學 Go 語言》第 15 課 —— 反射Go反射
- 《快學 Go 語言》第 5 課 —— 神奇的切片Go
- 《快學 Go 語言》第 1 課 —— Hello WorldGo
- 《快學 Go 語言》第 5 課 —— 靈活的切片Go
- 《快學 Go 語言》第 4 課 —— 低調的陣列Go陣列
- 《快學 Go 語言》第 12 課 —— 神秘的地下通道Go
- 《快學 Go 語言》第 2 課 —— 變數基礎Go變數
- 《快學 Go 語言》第 3 課 —— 分支與迴圈Go
- 《快學 Go 語言》第 13 課 —— 併發與安全Go
- 《快學 Go 語言》第 16 課 —— 包管理 GOPATH 和 VendorGo
- 《快學 Go 語言》第 14 課 —— 魔術變性指標Go指標
- 《快學 Go 語言》第 11 課 —— 千軍萬馬跑協程Go
- 《快學 Go 語言》第 8 課 —— 程式大廈是如何構建起來的Go
- 《快學 Go 語言》第 2 課 —— 變數什麼的最討厭了Go變數
- 高階語言程式設計課程第7次個人作業程式設計
- Go語言學習(7) - 運算子Go
- 非常適合GO語言新手學習的《Go語言從入門到實戰——簡明高效的Go語言實戰指南》課程——推薦分享Go
- go語言學習Go
- 【搞定Go語言】第2天4:Go語言基礎之流程控制Go
- golang 學習筆記:第 1 節:GO 語言介紹Golang筆記
- Go 語言手寫本地 LRU 快取Go快取
- 高階語言程式設計課程第9次個人作業程式設計
- Go語言學習筆記 - PART7 - 結構體Go筆記結構體
- Go語言學習查缺補漏ing Day7Go
- go語言學習-介面Go
- go語言學習-goroutineGo
- Go語言學習——mapGo
- 慕課網go語言體系課搶先體驗Go
- 四種命名介紹:駝峰、帕斯卡、蛇形、烤串(肉串)
- GO語言學習筆記之mac環境go語言配置Go筆記Mac
- Go語言入門經典第18章Go
- go培訓課程都學什麼?go語言框架學習:xorm框架知識介紹Go框架ORM