說到string
類型,我們往往都能很熟練地對它進行各種處理,包括迭代、隨機訪問和匹配等等操作。然而在工作中,我發現迭代一個字符串產生的字符的類型與隨機訪問一個字符的類型卻並不相同,為什么會這么奇怪呢?於是我決定一探究竟
string 簡析
在Golang中,字符串本質上看一看做一個只讀的字節切片(僅比切片少了一個Cap屬性)。它的底層結構我們可以查看reflect.StringHeader
得到:
type StringHeader struct {
Data uintptr
Len int
}
例如針對字符串"你好"
,其在內存中的表示如下圖所示:
Go的源文件默認使用UTF-8編碼,所有的字符串字面量一般也是UTF-8編碼的,故這里的你
編碼為\xe4\xbd\xa0
,好
編碼為\xe5\xa5\xbd
。UTF-8編碼不是我們討論的重點,具體可參考這篇博客。
這里我們運行下述代碼
s := []byte{0xe4, 0xbd, 0xa0}
fmt.Printf("char is %s", string(s))
得到運行結果char is 你
。
雖然字符串並非切片,但是支持切片操作。對於同一字面量,不同的字符串變量指向相同的底層數組,這是因為字符串是只讀的,為了節省內存,相同字面量的字符串通常對應於同一字符串常量。例如:
s := "hello, world"
s1 := "hello, world"
s2 := "hello, world"[7:]
fmt.Printf("%d \n", (*reflect.StringHeader)(unsafe.Pointer(&s)).Data) // 17598361
fmt.Printf("%d \n", (*reflect.StringHeader)(unsafe.Pointer(&s1)).Data) // 17598361
fmt.Printf("%d \n", (*reflect.StringHeader)(unsafe.Pointer(&s2)).Data) // 17598368
可以看到,三者均指向同一個底層數組。對於s1, s2由於是同一字符串常量hello, world
,故指向一個底層數組,以h
為起始位置;而s2是字面量hello, world
的切片操作后生成的字符串,也指向同一字節底層數組,不過是以w
為起始位置。
迭代字符串
當我們使用for range
迭代字符串時,每次迭代Go都會用UTF-8解碼出一個rune
類型的字符,且索引為當前rune
的起始位置(以字節為最下單位)。
for index, char := range "你好" {
fmt.Printf("start at %d, Unicode = %U, char = %c\n", index, char, char)
}
得到運行結果
start at 0, Unicode = U+4F60, char = 你
start at 3, Unicode = U+597D, char = 好
隨機訪問字符串
當我們用下標訪問字符串時,返回的值為單個字節,而我們直覺中,應該返回一個字符才合理。這還是因為string
的后端數組是一個字節切片而非一個字符切片
s := "你好"
fmt.Printf("s[%d] = %q, hex = %x, Unicode = %U", 1, s[1], s[1], s[1])
得到運行結果
s[1] = '½', hex = bd, Unicode = U+00BD
這里我們打印出來索引位置為1的字節,為0xbd
,其Unicode為U+00BD
, 代表的字符為½
。(你可以通過這里查詢)
到底什么是rune、字符和字節
字節:即byte,它由8個位組成,即1byte = 8bit,是計算機中的基本計量單位,在Go中為byte
類型,其實際上為uint8
的別名
字符:字符的概念比較模糊,在Unicode中通常用code point來表示。在我的理解里,是一種信息單元(例如一個符號、字母等)
rune:其實際上是int32
的別名,但是為了方便將字符與整數值區分開,從而新增了rune類型代表一個字符。
總結
- 通過下標訪問字符串時,返回的是一個字節,這往往與我們的直覺相背。所以,如果你一定要通過下標訪問字符串,可以先將其轉換為
[]rune
類型 - 字符串可以看做是一個只讀字節切片, 支持切片操作。
參考
- https://chorer.github.io/2019/09/16/CB-深入理解計算機系統cp1/
- https://draveness.me/golang/datastructure/golang-string.html
- https://berryjam.github.io/2018/03/從golang字符串string遍歷說起/
- https://en.wikipedia.org/wiki/Code_point
- https://github.com/chai2010/advanced-go-programming-book/blob/master/ch1-basic/ch1-03-array-string-and-slice.md