Go語(yǔ)言中的UTF-8實(shí)現(xiàn)
計(jì)算機(jī)剛誕生的時(shí)候,計(jì)算機(jī)內(nèi)的字符可以全部由 ASCII 來(lái)表示,ASCII 字符的長(zhǎng)度是 7 位,可以表示 128 個(gè)字符,對(duì)于美國(guó)等國(guó)家來(lái)說(shuō)是夠了,但是對(duì)于世界上的其他國(guó)家,特別是東亞國(guó)家,文字不是由字母組成,漢字就有幾萬(wàn)個(gè),ASCII 碼根本不夠用。
字符本質(zhì)就是對(duì)應(yīng)計(jì)算機(jī)中的一個(gè)數(shù)值,既然不夠用,那么解決方法就是把這個(gè)范圍擴(kuò)大,Unicode 的出現(xiàn)就解決了這個(gè)問(wèn)題,它包括了世界上所有的字符,每一個(gè)字符都對(duì)應(yīng)一個(gè)數(shù)值,這個(gè)數(shù)值被稱(chēng)之為 Unicode 碼點(diǎn)。
但是 Unicode 也不是沒(méi)有缺點(diǎn),因?yàn)楸硎镜姆秶?,所以每一個(gè) Unicode 都需要 4 個(gè)字節(jié)來(lái)表示,但是對(duì)于原本的 ASCII 編碼,本來(lái)只需要一個(gè)字節(jié),現(xiàn)在也需要 4個(gè)字節(jié),這樣會(huì)浪費(fèi)很多存儲(chǔ)。
UTF-8 的出現(xiàn)解決了這個(gè)問(wèn)題,它解決問(wèn)題的思路是讓每個(gè)字符選擇自己的大小,需要多少字節(jié)就用多少。對(duì)于占不同字節(jié)的字符,有不同的表示格式:
- 1 字節(jié):0xxxxxxx
- 2 字節(jié):110xxxxx 10xxxxxx
- 3 字節(jié):1110xxxx 10xxxxxx 10xxxxxx
- 4 字節(jié):11110xxx 10 xxxxxx 10xxxxxx 10xxxxxx
通過(guò)識(shí)別每個(gè)字符串的頭部來(lái)判斷占幾個(gè)字節(jié)。
每個(gè) Unicode 字符都對(duì)應(yīng)一個(gè)碼點(diǎn),在字符串中,可以對(duì)碼點(diǎn)進(jìn)行轉(zhuǎn)義,使用 \uhhhh 表示 16 位碼點(diǎn),使用 \Uhhhhhhhh 來(lái)表示 32 位碼點(diǎn),每一個(gè) h 都代表一個(gè)十六進(jìn)制的數(shù)字。
這里有一點(diǎn)比較特殊,對(duì)于碼點(diǎn)值小于 256 的文字符號(hào)可以使用單個(gè)十六進(jìn)制的數(shù)字來(lái)表示,比如 'A' 可以使用 '\x41' 來(lái)表示,對(duì)于大于 256 的碼點(diǎn),就必須使用 \u 或者 \U 來(lái)轉(zhuǎn)義。
Go 語(yǔ)言對(duì)于 UTF-8 的支持很好,這里有一點(diǎn)很有意思,Go 語(yǔ)言的兩位作者 Ken Thompson 和 Rob Pike 同時(shí)也是 UTF-8 的發(fā)明者,Go 語(yǔ)言對(duì) UTF-8 的支持贏在起跑線。
Go 語(yǔ)言總是使用 UTF-8 來(lái)處理源文件,同時(shí)也是優(yōu)先使用 UTF-8 來(lái)處理字符串。所以上面說(shuō)到的那些 Unicode 字符的轉(zhuǎn)義被 Go 直接處理,比如下面三個(gè)字符串在 Go 語(yǔ)言中是等價(jià)的:
"世界" "\u4e16\u754c" "\U00004e16\U0000u754c"
Go 字符串使用只讀的 []byte 來(lái)存儲(chǔ),所以字符串值是不變的,這樣做更安全,效率也很高:
s := "left root" t := s s += ", right root" fmt.Println(s) // left root, right root fmt.Println(t) // left root
在上面的例子中, s 的值出現(xiàn)了變化,但是 t 的值還是舊的字符串。由于是 [] byte 是 slice 類(lèi)型,所以字符串的截取操作效率很高,但是在字符串截取的過(guò)程中,就會(huì)出現(xiàn)一些坑。
Go 中的字符串底層使用了只讀的 []byte 來(lái)存儲(chǔ),所以**本質(zhì)上 Go 語(yǔ)言中的字符串是使用字節(jié)來(lái)表示,而不是字符表示,**理解這一點(diǎn)很重要。
str := "hello world" fmt.Println(str[:2]) // he str = "你好,世界" fmt.Println(str[:2]) // ��,這個(gè)符號(hào)用來(lái)表示 UTF-8 里面的未知字符,碼點(diǎn)是
非 ASCII 碼的字符一般占用的字節(jié)會(huì)超過(guò)一個(gè),如果直接截取,就會(huì)導(dǎo)致截取不到正確的位置,從而亂碼。在上面的例子中,一個(gè)中文字符占 3 個(gè)字節(jié),只有嚴(yán)格按照字節(jié)數(shù)來(lái)截取才能獲取到顯示正常的字符:
str = "你好,世界" fmt.Println(str[:3]) // 你
那么在這個(gè)時(shí)候,如果要按照字符截取,就需要把字符串轉(zhuǎn)成 []rune,每個(gè) rune 都代表一個(gè) UTF-8 中的碼點(diǎn),對(duì) []rune 按照字符截取就不會(huì)出現(xiàn)亂碼:
str = "你好,世界" runeStr := []rune(str) fmt.Println(string(runeStr[:1])) // 你
把字符串轉(zhuǎn)成 []rune,就是把字符串轉(zhuǎn)成 UTF-8 碼點(diǎn),而不是 []byte,rune 其實(shí)就是 int32 類(lèi)型。
Go 語(yǔ)言中有一個(gè)專(zhuān)門(mén) unicode/utf8 包來(lái)處理 utf8 字符。由于每個(gè)字符占據(jù)的字節(jié)可能不一樣,所以字符數(shù)和字節(jié)數(shù)大小是兩回事:
s := "Hello, 世界" // 逗號(hào)是半角符號(hào) fmt.Println(len(s)) // 13 fmt.Println(utf8.RuneCountInString(s)) // 9
如果要獲取字符占據(jù)的總字節(jié)數(shù),就使用 len 方法,如果需要計(jì)算字符的個(gè)數(shù),那就需要使用 utf8.RuneCountInString 方法。
這個(gè)包里面還提供了其他常用函數(shù):
// 判斷是否符合 utf8 編碼: func Valid(p []byte) bool func ValidRune(r rune) bool func ValidString(s string) bool // 判斷 rune 所占的字節(jié)數(shù) func RuneLen(r rune) int // 判斷字節(jié)串或者字符串中的 rune 字符數(shù) func RuneCount(p []byte) int func RuneCountInString(s string) int // 對(duì) rune 的編碼和解碼 func EncodeRune(p []byte, r rune) int func DecodeRune(p []byte) (r rune, size int) func DecodeRuneInString(s string) (r rune, size int) func DecodeLastRune(p []byte) (r rune, size int) func DecodeLastRuneInString(s string) (r rune, size int)
除了 utf8 包之外, unicode 包對(duì)提供了一系列 IsXX 函數(shù)來(lái) rune 的檢查:
func Is(rangeTab *RangeTable, r rune) bool // 是否是 RangeTable 類(lèi)型的 func In(r rune, ranges ...*RangeTable) bool // 是否是 ranges 中任意一個(gè)類(lèi)型的字符 func IsControl(r rune) bool // 是否是控制字符 func IsDigit(r rune) bool // 是否是阿拉伯?dāng)?shù)字字符,即 0-9 func IsGraphic(r rune) bool // 是否是圖形字符 func IsLetter(r rune) bool // 是否是字母 func IsLower(r rune) bool // 是否是小寫(xiě)字符 func IsMark(r rune) bool // 是否是符號(hào)字符 func IsNumber(r rune) bool // 是否是數(shù)字字符,包含羅馬數(shù)字 func IsOneOf(ranges []*RangeTable, r rune) bool // 是否是 RangeTable 中的一個(gè) func IsPrint(r rune) bool // 是否是可打印字符 func IsPunct(r rune) bool // 是否是標(biāo)點(diǎn)符號(hào) func IsSpace(r rune) bool // 是否是空格 func IsSymbol(r rune) bool // 是否符號(hào)字符 func IsTitle(r rune) bool // 字符串中的每個(gè)單詞的第一個(gè)字符是否是大寫(xiě) func IsUpper(r rune) bool // 是否是大寫(xiě)字符
RangeTable 是對(duì)所有 Unicode 字符的分類(lèi),比如驗(yàn)證一個(gè)字符是否是漢字:
r := '中' result := unicode.Is(unicode.Han, r) fmt.Println(result) // true
其中 unicode.Han 就是 RangeTable 類(lèi)型,表示漢字。
到此這篇關(guān)于Go語(yǔ)言中的UTF-8實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Go語(yǔ)言UTF-8內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go語(yǔ)言中os包的用法實(shí)戰(zhàn)大全
Go在os中提供了文件的基本操作,包括通常意義的打開(kāi)、創(chuàng)建、讀寫(xiě)等操作,除此以外為了追求便捷以及性能上,Go還在io/ioutil以及bufio提供一些其他函數(shù)供開(kāi)發(fā)者使用,這篇文章主要給大家介紹了關(guān)于go語(yǔ)言中os包用法的相關(guān)資料,需要的朋友可以參考下2024-02-02
golang中select語(yǔ)句的簡(jiǎn)單實(shí)例
Go的select語(yǔ)句是一種僅能用于channl發(fā)送和接收消息的專(zhuān)用語(yǔ)句,此語(yǔ)句運(yùn)行期間是阻塞的,下面這篇文章主要給大家介紹了關(guān)于golang中select語(yǔ)句的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06
Go語(yǔ)言HTTPServer開(kāi)發(fā)的六種方式小結(jié)
Golang的Server開(kāi)發(fā)顯得非常簡(jiǎn)單,有很多種方式,本文就介紹了Go語(yǔ)言HTTPServer開(kāi)發(fā)的六種方式,具有一定的參考價(jià)值,感興趣的可以了解一下2021-11-11
Go?復(fù)合類(lèi)型之字典類(lèi)型使用教程示例
這篇文章主要為大家介紹了Go復(fù)合類(lèi)型之字典類(lèi)型使用教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
詳解golang 模板(template)的常用基本語(yǔ)法
這篇文章主要介紹了詳解golang 模板(template)的常用基本語(yǔ)法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
Golang中int,?int8,?int16,?int32,?int64和uint區(qū)別淺析
go語(yǔ)言中的int的大小是和操作系統(tǒng)位數(shù)相關(guān)的,如果是32位操作系統(tǒng),int類(lèi)型的大小就是4字節(jié),如果是64位操作系統(tǒng),int類(lèi)型的大小就是8個(gè)字節(jié),下面這篇文章主要給大家介紹了關(guān)于Golang中int,?int8,?int16,?int32,?int64和uint區(qū)別的相關(guān)資料,需要的朋友可以參考下2022-11-11
Go語(yǔ)言WaitGroup使用時(shí)需要注意的坑
Go語(yǔ)言中WaitGroup的用途是它能夠一直等到所有的goroutine執(zhí)行完成,并且阻塞主線程的執(zhí)行,直到所有的goroutine執(zhí)行完成。之前一直使用也沒(méi)有問(wèn)題,但最近通過(guò)同事的一段代碼引起了關(guān)于WaitGroup的注意,下面這篇文章就介紹了WaitGroup使用時(shí)需要注意的坑及填坑。2016-12-12

