Golang中的Unicode與字符串示例詳解
背景:
在我們使用Golang進(jìn)行開(kāi)發(fā)過(guò)程中,總是繞不開(kāi)對(duì)字符或字符串的處理,而在Golang語(yǔ)言中,對(duì)字符和字符串的處理方式可能和其他語(yǔ)言不太一樣,比如Python或Java類(lèi)的語(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 類(lèi)型的值被轉(zhuǎn)換為 []rune 類(lèi)型值的時(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ì)書(shū)面字符和文本的字符編碼標(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ù)的范圍被稱(chēng)為代碼空間。在代碼空間之內(nèi),每一個(gè)特定的整數(shù)都被稱(chēng)為一個(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 版本開(kāi)始,已經(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 的縮寫(xiě)。而 UCS 又是 Universal Character Set 的縮寫(xiě),但也可以代表 Unicode Character Set。所以,UTF 也可以被翻譯為 Unicode 轉(zhuǎn)換格式。它代表的是字符與字節(jié)序列之間的轉(zhuǎn)換方式。
在這幾種編碼格式的名稱(chēng)中,“-” 右邊的整數(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)稱(chēng)后者為 UTF-8 編碼值。
string類(lèi)型的底層存儲(chǔ)
在 Go 語(yǔ)言中,一個(gè) string 類(lèi)型的值既可以被拆分為一個(gè)包含多個(gè)字符的序列,也可以被拆分為一個(gè)包含多個(gè)字節(jié)的序列。
前者可以由一個(gè)以 rune 為元素類(lèi)型的切片來(lái)表示,而后者則可以由一個(gè)以 byte 為元素類(lèi)型的切片代表。
rune 是 Go 語(yǔ)言特有的一個(gè)基本數(shù)據(jù)類(lèi)型,它的一個(gè)值就代表一個(gè)字符,即:一個(gè)Unicode 字符(再通俗點(diǎn),就是一個(gè)中文字符,占3byte)。
從Golang語(yǔ)言的源碼(https://github.com/golang/go/blob/master/src/builtin/builtin.go#L92)中我們其實(shí)可以知道,rune類(lèi)型底層其實(shí)是一個(gè)int32類(lèi)型。
我們已經(jīng)知道,UTF-8 編碼方案會(huì)把一個(gè) Unicode 字符編碼為一個(gè)長(zhǎng)度在[1, 4] 范圍內(nèi)的字節(jié)序列,也就是說(shuō),一個(gè) rune 類(lèi)型的值會(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類(lèi)型的值時(shí),其中每個(gè)字符都會(huì)成為一個(gè)獨(dú)立的rune類(lèi)型的元素值。而每個(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 類(lèi)型的值會(huì)由若干個(gè) Unicode 字符組成,每個(gè) Unicode 字符都可以由一個(gè) rune 類(lèi)型的值來(lái)承載。這些字符在底層都會(huì)被轉(zhuǎn)換為 UTF-8 編碼值,而這些 UTF-8 編碼值又會(huì)以字節(jié)序列的形式表達(dá)和存儲(chǔ)。
所以,一個(gè) string 類(lèi)型的值在底層就是一個(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 字符,其類(lèi)型會(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)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Go語(yǔ)言構(gòu)建RESTful API服務(wù)
在實(shí)際開(kāi)發(fā)項(xiàng)目中,你編寫(xiě)的服務(wù)可以被其他服務(wù)使用,這樣就組成了微服務(wù)的架構(gòu);也可以被前端調(diào)用,這樣就可以前后端分離。那么,本文主要介紹什么是 RESTful API,以及 Go 語(yǔ)言是如何玩轉(zhuǎn) RESTful API 的2021-07-07go?singleflight緩存雪崩源碼分析與應(yīng)用
這篇文章主要為大家介紹了go?singleflight緩存雪崩源碼分析與應(yīng)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09Golang 實(shí)現(xiàn)Socket服務(wù)端和客戶端使用TCP協(xié)議通訊
這篇文章主要介紹了Golang 實(shí)現(xiàn)Socket服務(wù)端和客戶端使用TCP協(xié)議通訊,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12Go語(yǔ)言異步API設(shè)計(jì)的扇入扇出模式詳解
這篇文章主要為大家介紹了Go語(yǔ)言異步API設(shè)計(jì)的扇入扇出模式示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08GoLang日志監(jiān)控系統(tǒng)實(shí)現(xiàn)
這篇文章主要介紹了GoLang日志監(jiān)控系統(tǒng)的實(shí)現(xiàn)流程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-12-12使用Golang?Validator包實(shí)現(xiàn)數(shù)據(jù)驗(yàn)證詳解
在開(kāi)發(fā)過(guò)程中,數(shù)據(jù)驗(yàn)證是一個(gè)非常重要的環(huán)節(jié),而golang中的Validator包是一個(gè)非常常用和強(qiáng)大的數(shù)據(jù)驗(yàn)證工具,提供了簡(jiǎn)單易用的API和豐富的驗(yàn)證規(guī)則,下面我們就來(lái)看看Validator包的具體使用吧2023-12-12Go語(yǔ)言k8s?kubernetes使用leader?election實(shí)現(xiàn)選舉
這篇文章主要為大家介紹了Go語(yǔ)言?k8s?kubernetes?使用leader?election選舉,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10Golang 負(fù)載均衡算法實(shí)現(xiàn)示例
在Go語(yǔ)言中,負(fù)載均衡算法通常由代理、反向代理或者應(yīng)用層負(fù)載均衡器來(lái)實(shí)現(xiàn),在這些實(shí)現(xiàn)中,有一些經(jīng)典的負(fù)載均衡算法,跟隨本文來(lái)一一探究2024-01-01go mod tidy加載模塊超時(shí)的問(wèn)題及解決
go mod tidy加載模塊超時(shí)的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09