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