【Go入門向】搞懂 string 與 rune 的真正差別
在 Go 裡用 str[i] 取值真的拿到的是字元嗎?別被表象誤導!本文深入解析 string 與 rune 的本質與使用情境,讓你一次搞懂 byte、UTF-8、與 Unicode 的差異。
toc
情境
PM:這個留言板幫我限制最多只能輸入 100 字喔。
小明:好的!(心想:超級簡單)
if len(comment) > 100 {
return errors.New("留言超過上限")
}
試想上面程式碼可能會遇到什麼問題?
string 的本質
試問 c 會印出什麼?
func main() {
s := "Hello"
c := s[0]
fmt.Println(c) // ??
}
你可能以為是 H
,但實際上印出的是 72
根據官方文件所述:
A string value is a (possibly empty) sequence of bytes.
也就是說 Go 的字串本質上是 bytes 序列,所以上述程式碼 s[0]
印出的會是 H 的 utf-8 值。
UTF-8 與 Unicode 又是什麼?
電腦的世界沒有文字,只有 0 跟 1 ,那要怎麼表示各種文字呢?
早期各國創了各自字元的對照表,但就會發生在 A 國有這個字,但 B 國沒有,就會顯示亂碼,為了解決這樣的問題,Unicode 誕生了。
Unicode 幫各國字元編碼,讓大家可以統一語言,每個字元對應一個碼位 code point,比如說中文的「你」,對應到 Unicode 裡就是 \u4f60
。
可以使用 Unicode 字元轉換工具來轉換看看。
那什麼是 UTF-8?如果你是前端人員,在寫 html 時,會看到有這一段:
<head>
<meta charset="UTF-8" />
</head>
UTF-8 其實就是一種編碼方式,用來將 Unicode 字元集的碼位轉換成二進位,他還有其他兄弟,如:UTF-16,但考量空間效率,UTF-8 是目前最廣泛使用的編碼方式。
rune 是什麼?
rune 在 Go 中,其實就是 Unicode 的碼位 (code point),其實也等同於 int32
var r rune = '🤡'
fmt.Println(r) // 129313
fmt.Printf("%U", r) // U+1F921
常見陷阱
陷阱 1:len(str) 回傳的是 bytes 數,不是字元數
str := "你好"
fmt.Println(len(str)) // 6,不是 2!
如果想獲得字串的 rune 數量,應該改使用 unicode/utf8
package:
import (
"fmt"
"unicode/utf8"
)
func main() {
str := "你好"
fmt.Println(utf8.RuneCountInString(str))
}
陷阱 2:for loop
str := "早安你好"
for i := range str {
fmt.Printf("position: %d: %c\n", i, str[i])
}
// position: 0: æ
// position: 3: å
// position: 6: ä
// position: 9: å
可以看到印出的 index 並不是依照順序印出 0,1,2,3,這是因為 for loop 迭代的是 rune
而非 byte
。
如果要正確取得 index
與字元的話,可以先將字串轉成 []rune
:
str := "早安你好"
runes := []rune(str)
for i, r := range runes {
fmt.Printf("position: %d: %c\n", i, r)
}
Summary
Go 的 string 儲存的是 bytes,而不是字元。 只有 rune 才是字元。
如果要處理 string,建議使用 unicode/utf8 或 strings 等官方套件比較保險。