背景:
在我們使用Golang進(jìn)行開發(fā)過(guò)程中,總是繞不開對(duì)字符或字符串的處理,而在Golang語(yǔ)言中,對(duì)字符和字符串的處理方式可能和其他語(yǔ)言不太一樣,比如Python或Java類的語(yǔ)言,本篇文章分享一些Golang語(yǔ)言下的Unicode和字符串編碼。
Go語(yǔ)言字符編碼
注意: 在Golang語(yǔ)言中的標(biāo)識(shí)符可以包含 " 任何Unicode編碼可以標(biāo)識(shí)的字母字符 "。
被轉(zhuǎn)換的整數(shù)值應(yīng)該可以代表一個(gè)有效的 Unicode 代碼點(diǎn),否則轉(zhuǎn)換的結(jié)果就將會(huì)是 "�",即:一個(gè)僅由高亮的問(wèn)號(hào)組成的字符串值。
另外,當(dāng)一個(gè) string 類型的值被轉(zhuǎn)換為 []rune 類型值的時(shí)候,其中的字符串會(huì)被拆分成一個(gè)一個(gè)的 Unicode 字符。
顯然,Go 語(yǔ)言采用的字符編碼方案從屬于 Unicode 編碼規(guī)范。更確切地說(shuō),Go 語(yǔ)言的代碼正是由 Unicode 字符組成的。Go 語(yǔ)言的所有源代碼,都必須按照 Unicode 編碼規(guī)范中的 UTF-8 編碼格式進(jìn)行編碼。
換句話說(shuō),Go 語(yǔ)言的源碼文件必須使用 UTF-8 編碼格式進(jìn)行存儲(chǔ)。如果源碼文件中出現(xiàn)了非 UTF-8 編碼的字符,那么在構(gòu)建、安裝以及運(yùn)行的時(shí)候,go 命令就會(huì)報(bào)告錯(cuò)誤 " illegal UTF-8 encoding "。
ASCII 編碼
ASCII 編碼方案使用單個(gè)字節(jié)(byte)的二進(jìn)制數(shù)來(lái)編碼一個(gè)字符。標(biāo)準(zhǔn)的 ASCII 編碼用一個(gè)字節(jié)的最高比特(bit)位作為奇偶校驗(yàn)位,而擴(kuò)展的 ASCII 編碼則將此位也用于表示字符。ASCII 編碼支持的可打印字符和控制字符的集合也被叫做 ASCII 編碼集。
我們所說(shuō)的 Unicode 編碼規(guī)范,實(shí)際上是另一個(gè)更加通用的、針對(duì)書面字符和文本的字符編碼標(biāo)準(zhǔn)。它為世界上現(xiàn)存的所有自然語(yǔ)言中的每一個(gè)字符,都設(shè)定了一個(gè)唯一的二進(jìn)制編碼。
它定義了不同自然語(yǔ)言的文本數(shù)據(jù)在國(guó)際間交換的統(tǒng)一方式,并為全球化軟件創(chuàng)建了一個(gè)重要的基礎(chǔ)。
Unicode 編碼規(guī)范以 ASCII 編碼集為出發(fā)點(diǎn),并突破了 ASCII 只能對(duì)拉丁字母進(jìn)行編碼的限制。它不但提供了可以對(duì)世界上超過(guò)百萬(wàn)的字符進(jìn)行編碼的能力,還支持所有已知的轉(zhuǎn)義序列和控制代碼。
我們都知道,在計(jì)算機(jī)系統(tǒng)的內(nèi)部,抽象的字符會(huì)被編碼為整數(shù)。這些整數(shù)的范圍被稱為代碼空間。在代碼空間之內(nèi),每一個(gè)特定的整數(shù)都被稱為一個(gè)代碼點(diǎn)。
一個(gè)受支持的抽象字符會(huì)被映射并分配給某個(gè)特定的代碼點(diǎn),反過(guò)來(lái)講,一個(gè)代碼點(diǎn)總是可以被看成一個(gè)被編碼的字符。
Unicode 編碼規(guī)范通常使用十六進(jìn)制表示法來(lái)表示 Unicode 代碼點(diǎn)的整數(shù)值,并使用 “U+” 作為前綴。比如,英文字母字符 “a” 的 Unicode 代碼點(diǎn)是 U+0061。在 Unicode 編碼規(guī)范中,一個(gè)字符能且只能由與它對(duì)應(yīng)的那個(gè)代碼點(diǎn)表示。
Unicode 編碼規(guī)范現(xiàn)在的最新版本是 11.0,并會(huì)于 2019 年 3 月發(fā)布 12.0 版本。而 Go 語(yǔ)言從 1.10 版本開始,已經(jīng)對(duì) Unicode 的 10.0 版本提供了全面的支持。對(duì)于絕大多數(shù)的應(yīng)用場(chǎng)景來(lái)說(shuō),這已經(jīng)完全夠用了。
Unicode 編碼規(guī)范提供了三種不同的編碼格式,即:UTF-8、UTF-16 和 UTF-32。其中的 UTF 是 UCS Transformation Format 的縮寫。而 UCS 又是 Universal Character Set 的縮寫,但也可以代表 Unicode Character Set。所以,UTF 也可以被翻譯為 Unicode 轉(zhuǎn)換格式。它代表的是字符與字節(jié)序列之間的轉(zhuǎn)換方式。
在這幾種編碼格式的名稱中,“-” 右邊的整數(shù)的含義是,以多少個(gè)比特位作為一個(gè)編碼單元。以 UTF-8 為例,它會(huì)以 8 個(gè)比特,也就是一個(gè)字節(jié),作為一個(gè)編碼單元。并且,它與標(biāo)準(zhǔn)的 ASCII 編碼是完全兼容的。也就是說(shuō),在 [0x00, 0x7F] 的范圍內(nèi),這兩種編碼表示的字符都是相同的。這也是 UTF-8 編碼格式的一個(gè)巨大優(yōu)勢(shì)。
UTF-8 是一種可變寬的編碼方案。換句話說(shuō),它會(huì)用一個(gè)或多個(gè)字節(jié)的二進(jìn)制數(shù)來(lái)表示某個(gè)字符,最多使用四個(gè)字節(jié)。比如,對(duì)于一個(gè)英文字符,它僅用一個(gè)字節(jié)的二進(jìn)制數(shù)就可以表示,而對(duì)于一個(gè)中文字符,它需要使用三個(gè)字節(jié)才能夠表示。不論怎樣,一個(gè)受支持的字符總是可以由 UTF-8 編碼為一個(gè)字節(jié)序列。以下會(huì)簡(jiǎn)稱后者為 UTF-8 編碼值。
string類型的底層存儲(chǔ)
在 Go 語(yǔ)言中,一個(gè) string 類型的值既可以被拆分為一個(gè)包含多個(gè)字符的序列,也可以被拆分為一個(gè)包含多個(gè)字節(jié)的序列。
前者可以由一個(gè)以 rune 為元素類型的切片來(lái)表示,而后者則可以由一個(gè)以 byte 為元素類型的切片代表。
rune 是 Go 語(yǔ)言特有的一個(gè)基本數(shù)據(jù)類型,它的一個(gè)值就代表一個(gè)字符,即:一個(gè)Unicode 字符(再通俗點(diǎn),就是一個(gè)中文字符,占3byte)。
從Golang語(yǔ)言的源碼(https://github.com/golang/go/blob/master/src/builtin/builtin.go#L92)中我們其實(shí)可以知道,rune類型底層其實(shí)是一個(gè)int32類型。
我們已經(jīng)知道,UTF-8 編碼方案會(huì)把一個(gè) Unicode 字符編碼為一個(gè)長(zhǎng)度在[1, 4] 范圍內(nèi)的字節(jié)序列,也就是說(shuō),一個(gè) rune 類型的值會(huì)由四個(gè)字節(jié)寬度的空間來(lái)存儲(chǔ)。它的存儲(chǔ)空間總是能夠存下一個(gè) UTF-8 編碼值。
我們可以看如下代碼:
func unicodeAndUtf8() {
tempStr := "BGBiao 的SRE人生."
fmt.Printf("string:%q\n",tempStr)
fmt.Printf("rune(char):%q\n",[]rune(tempStr))
fmt.Printf("rune(hex):%x\n",[]rune(tempStr))
fmt.Printf("bytes(hex):% x\n",[]byte(tempStr))
}
對(duì)應(yīng)輸出的效果如下:
string:"BGBiao 的SRE人生."
rune(char):['B' 'G' 'B' 'i' 'a' 'o' ' ' '的' 'S' 'R' 'E' '人' '生' '.']
rune(hex):[42 47 42 69 61 6f 20 7684 53 52 45 4eba 751f 2e]
bytes(hex):42 47 42 69 61 6f 20 e7 9a 84 53 52 45 e4 ba ba e7 94 9f 2e
第二行輸出可以看到字符串在被轉(zhuǎn)換為[]rune類型的值時(shí),其中每個(gè)字符都會(huì)成為一個(gè)獨(dú)立的rune類型的元素值。而每個(gè)rune底層的值都是采用UTF-8編碼值來(lái)表達(dá)的,所以第三行的輸出,我們采用16進(jìn)制數(shù)來(lái)表示上述字符串,每一個(gè)16進(jìn)制的字符分別表示一個(gè)字符,我們可以看到,當(dāng)遇到中文字符時(shí),由于底層存儲(chǔ)需要更大的空間,所以使用的16進(jìn)制數(shù)字也比較大,比如4eba和751f分別代表人和生。
但其實(shí),當(dāng)我們將整個(gè)字符的UTF-8編碼值都拆成響應(yīng)的字節(jié)序列時(shí),就變成了第四行的輸出,可以看到一個(gè)中文字符其實(shí)底層是占用了三個(gè)byte,比如e4 ba ba和e7 94 9f分別對(duì)應(yīng)UFT-8編碼值的4eba和751f,也即中文字符中的人和生。
注意: 對(duì)于一個(gè)多字節(jié)的 UTF-8 編碼值來(lái)說(shuō),我們可以把它當(dāng)做一個(gè)整體轉(zhuǎn)換為單一的整數(shù),也可以先把它拆成字節(jié)序列,再把每個(gè)字節(jié)分別轉(zhuǎn)換為一個(gè)整數(shù),從而得到多個(gè)整數(shù)。
我們對(duì)上述字符串的底層編碼進(jìn)行圖形拆解:
總之,一個(gè) string 類型的值會(huì)由若干個(gè) Unicode 字符組成,每個(gè) Unicode 字符都可以由一個(gè) rune 類型的值來(lái)承載。這些字符在底層都會(huì)被轉(zhuǎn)換為 UTF-8 編碼值,而這些 UTF-8 編碼值又會(huì)以字節(jié)序列的形式表達(dá)和存儲(chǔ)。
所以,一個(gè) string 類型的值在底層就是一個(gè)能夠表達(dá)若干個(gè) UTF-8 編碼值的字節(jié)序列。
range遍歷字符串示例
注意: 帶有 range 子句的 for 語(yǔ)句會(huì)先把被遍歷的字符串值拆成一個(gè)字節(jié)序列,然后再試圖找出這個(gè)字節(jié)序列中包含的每一個(gè) UTF-8 編碼值,或者說(shuō)每一個(gè) Unicode 字符。因此在 range for 語(yǔ)句中,賦給第二個(gè)變量的值是UTF-8 編碼值代表的那個(gè) Unicode 字符,其類型會(huì)是 rune。
我們來(lái)看如下代碼:
func rangeString() {
tempStr := "BGBiao 人生"
for k,v := range tempStr {
fmt.Printf("%d : %q %x [% x]\n",k,v,[]rune(string(v)),[]byte(string(v)))
}
}
使用 for range 進(jìn)行遍歷字符串,得到如下結(jié)果:
0 : 'B' [42] [42]
1 : 'G' [47] [47]
2 : 'B' [42] [42]
3 : 'i' [69] [69]
4 : 'a' [61] [61]
5 : 'o' [6f] [6f]
6 : ' ' [20] [20]
7 : '人' [4eba] [e4 ba ba]
10 : '生' [751f] [e7 94 9f]
可以看到,遍歷字符串中的每個(gè)字符時(shí),對(duì)應(yīng)的表示方式和我們上圖中分析的是一致的,但是你有沒(méi)有發(fā)現(xiàn)一個(gè)小問(wèn)題呢?
即在遍歷過(guò)程中,最后一個(gè)字符生的索引一下從7變成了10,這是因?yàn)槿诉@個(gè)字符底層是由三個(gè)字節(jié)共同表達(dá)的,即[e4 ba ba],因此下一個(gè)字符的索引值就需要加3,而生的索引值也就變成了10而不是8。
所以,需要注意的是: for range 語(yǔ)句可以逐一的迭代出字符串值里的每個(gè)Unicode字符,但是相鄰的Unicode字符的索引值并不一定是連續(xù)的,這取決于前一個(gè)Unicode字符是否為單字節(jié)字符。
總結(jié)
到此這篇關(guān)于Golang中Unicode與字符串示例的文章就介紹到這了,更多相關(guān)Golang Unicode與字符串內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
您可能感興趣的文章:- golang 字符串比較是否相等的方法示例
- 解決Golang json序列化字符串時(shí)多了\的情況
- golang 獲取字符串長(zhǎng)度的案例
- golang 字符串拼接性能的對(duì)比分析
- 利用golang的字符串解決leetcode翻轉(zhuǎn)字符串里的單詞
- golang 字符串切片去重實(shí)例
- golang時(shí)間字符串和時(shí)間戳轉(zhuǎn)換的案例
- golang 如何替換掉字符串里面的換行符\n