解析Go?中的?rune?類型
剛接觸 Go 語言時(shí),就聽說有一個(gè)叫rune的數(shù)據(jù)類型,即使查閱過一些資料,對(duì)它的理解依舊比較模糊,加之對(duì)陌生事物的天然排斥,在之后很長一段時(shí)間的編程工作中,我都沒有讓它出現(xiàn)在我的代碼里。
逃避雖然有用,但是似乎有些可恥,想要成為一名成熟、優(yōu)秀的 Go 語言開發(fā)工程師,必須要有直面陌生事物并且成功運(yùn)用的勇氣和能力,帶著這樣的覺悟,讓我們一起走近rune,直視它!
了解一下,rune類型究竟是什么?
rune類型是 Go 語言的一種特殊數(shù)字類型。在builtin/builtin.go文件中,它的定義:type rune = int32;官方對(duì)它的解釋是:rune是類型int32的別名,在所有方面都等價(jià)于它,用來區(qū)分字符值跟整數(shù)值。使用單引號(hào)定義 ,返回采用 UTF-8 編碼的 Unicode 碼點(diǎn)。Go 語言通過rune處理中文,支持國際化多語言。
眾所周知,Go 語言有兩種類型聲明方式:一種叫類型定義聲明,另一種叫類型別名聲明。其中,別名的使用在大型項(xiàng)目重構(gòu)中作用最為明顯,它能解決代碼升級(jí)或遷移過程中可能存在的類型兼容性問題。而rune跟byte是 Go 語言中僅有的兩個(gè)類型別名,專門用來處理字符。當(dāng)然,我們也可以通過type關(guān)鍵字加等號(hào)的方式聲明更多的類型別名。
學(xué)習(xí)一下,rune類型怎么用?
我們知道,字符串由字符組成,字符的底層由字節(jié)組成,而一個(gè)字符串在底層的表示是一個(gè)字節(jié)序列。在 Go 語言中,字符可以被分成兩種類型處理:對(duì)占 1 個(gè)字節(jié)的英文類字符,可以使用byte(或者unit8);對(duì)占 1 ~ 4 個(gè)字節(jié)的其他字符,可以使用rune(或者int32),如中文、特殊符號(hào)等。
下面,我們通過示例應(yīng)用來具體感受一下。
統(tǒng)計(jì)帶中文字符串長度
// 使用內(nèi)置函數(shù) len() 統(tǒng)計(jì)字符串長度
fmt.Println(len("Go語言編程")) // 輸出:14 前面說到,字符串在底層的表示是一個(gè)字節(jié)序列。其中,英文字符占用 1 字節(jié),中文字符占用 3 字節(jié),所以得到的長度 14 顯然是底層占用字節(jié)長度,而不是字符串長度,這時(shí),便需要用到rune類型。
// 轉(zhuǎn)換成 rune 數(shù)組后統(tǒng)計(jì)字符串長度
fmt.Println(len([]rune("Go語言編程"))) // 輸出:6這回對(duì)了。很容易,我們解鎖了rune類型的第一個(gè)功能,即統(tǒng)計(jì)字符串長度。
- 截取帶中文字符串
如果想要截取字符串中 ”Go語言“ 這一段,考慮到底層是一個(gè)字節(jié)序列,或者說是一個(gè)數(shù)組,通常情況下,我們會(huì)這樣:
s := "Go語言編程" // 8=2*1+2*3 fmt.Println(s[0:8]) // 輸出:Go語言
結(jié)果符合預(yù)期。但是,按照字節(jié)的方式進(jìn)行截取,必須預(yù)先計(jì)算出需要截取字符串的字節(jié)數(shù),如果字節(jié)數(shù)計(jì)算錯(cuò)誤,就會(huì)顯示亂碼,比如這樣:
s := "Go語言編程" fmt.Println(s[0:7]) // 輸出:Go語?
此外,如果截取的字符串較長,那通過字節(jié)的方式進(jìn)行截取顯然不是一個(gè)高效準(zhǔn)確的辦法。那有沒有不用計(jì)算字節(jié)數(shù),簡(jiǎn)單又不會(huì)出現(xiàn)亂碼的方法呢?不妨試試這樣:
s := "Go語言編程" // 轉(zhuǎn)成 rune 數(shù)組,需要幾個(gè)字符,取幾個(gè)字符 fmt.Println(string([]rune(s)[:4])) // 輸出:Go語言
到這里,我們解鎖了rune類型的第二個(gè)功能,即截取字符串。
思考一下,為什么rune類型可以做到?
通過上面的示例,我們發(fā)現(xiàn)似乎在處理帶中文的字符串時(shí),都需要用到rune類型,這究竟是為什么呢?除了使用rune類型,還有其他方法嗎?
在深入思考之前,我們需要首先弄清楚string、byte、rune三者間的關(guān)系。
字符串在底層的表示是由單個(gè)字節(jié)組成的一個(gè)不可修改的字節(jié)序列,字節(jié)使用UTF-8[1]編碼標(biāo)識(shí)Unicode[2]文本。Unicode 文本意味著.go文件內(nèi)可以包含世界上的任意語言或字符,該文件在任意系統(tǒng)上打開都不會(huì)亂碼。UTF-8 是 Unicode 的一種實(shí)現(xiàn)方式,是一種針對(duì) Unicode 可變長度的字符編碼,它定義了字符串具體以何種方式存儲(chǔ)在內(nèi)存中。UFT-8 使用 1 ~ 4 為每個(gè)字符編碼。
Go 語言把字符分byte和rune兩種類型處理。byte是類型unit8的別名,用于存放占 1 字節(jié)的 ASCII 字符,如英文字符,返回的是字符原始字節(jié)。rune是類型int32的別名,用于存放多字節(jié)字符,如占 3 字節(jié)的中文字符,返回的是字符 Unicode 碼點(diǎn)值。如下圖所示:
s := "Go語言編程" // byte fmt.Println([]byte(s)) // 輸出:[71 111 232 175 173 232 168 128 231 188 150 231 168 139] // rune fmt.Println([]rune(s)) // 輸出:[71 111 35821 35328 32534 31243]
它們的對(duì)應(yīng)關(guān)系如下圖:

了解了這些,我們?cè)倩剡^來看看,剛才的問題是不是清楚明白很多?接下來,讓我們?cè)賮砜纯丛创a中是如何處理的,以u(píng)tf8.RuneCountInString()[3]函數(shù)為例。
示例:
// 統(tǒng)計(jì)字符串長度
fmt.Println(utf8.RuneCountInString("Go語言編程")) // 輸出:6源碼:
// RuneCountInString is like RuneCount but its input is a string.
func RuneCountInString(s string) (n int) {
// 調(diào)用 len() 函數(shù)得到字節(jié)數(shù)
ns := len(s)
for i := 0; i < ns; n++ {
c := s[i]
// 如碼點(diǎn)值小于 128,則為占 1 字節(jié)的 ASCII 字符(或者說英文字符),長度 + 1
if c < RuneSelf { // RuneSelf = 128
// ASCII fast path
i++
continue
}
// 查詢首字節(jié)信息表,得到中文占 3 字節(jié),所以這里的 x = 3
x := first[c]
// 判斷 x = 3,xx = 241(0xF1)
if x == xx {
i++ // invalid.
continue
}
// 提取有效的 UTF-8 字節(jié)長度編碼信息,size = 3
size := int(x & 7)
if i+size > ns {
i++ // Short or invalid.
continue
}
// 提取有效字節(jié)范圍
accept := acceptRanges[x>>4]
// accept.lo,accept.hi,表示 UTF-8 中第二字節(jié)的有效范圍
// locb = 0b10000000,表示 UTF-8 編碼非首字節(jié)的數(shù)值下限
// hicb = 0b10111111,表示 UTF-8 編碼非首字節(jié)的數(shù)值上限
if c := s[i+1]; c < accept.lo || accept.hi < c {
size = 1
} else if size == 2 {
} else if c := s[i+2]; c < locb || hicb < c {
size = 1
} else if size == 3 {
} else if c := s[i+3]; c < locb || hicb < c {
size = 1
}
i += size
}
return n
}調(diào)用該函數(shù)時(shí),傳入一個(gè)原始的字符串,代碼會(huì)根據(jù)每個(gè)字符的碼點(diǎn)大小判斷是否為 ASCII 字符,如果是,則算做 1 位;如果不是,則查詢首字節(jié)表,明確字符占用的字節(jié)數(shù),驗(yàn)證有效性后再進(jìn)行計(jì)數(shù)。
小小總結(jié)
在我看來,rune類型只是一種名稱叫法,表示用來處理長度大于 1 字節(jié)( 8 位)、不超過 4 字節(jié)( 32 位)的字符類型。但萬變不離其宗,我們使用函數(shù)時(shí),無論傳入?yún)?shù)的是原始字符串還是rune,最終都是對(duì)字節(jié)進(jìn)行處理??此颇吧氖挛?,沉下心了解到其本質(zhì)以后,才發(fā)現(xiàn)原來并不陌生,缺少的只是正視它的勇氣!
[1]
UTF-8:https://zh.wikipedia.org/wiki/UTF-8
[2]
Unicode:https://zh.wikipedia.org/wiki/Unicode
[3]
utf8.RuneCountInString():https://golang.org/src/unicode/utf8/utf8.go
到此這篇關(guān)于Go 中的 rune 類型的文章就介紹到這了,更多相關(guān)Go rune 類型內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go-客戶信息關(guān)系系統(tǒng)的實(shí)現(xiàn)
這篇文章主要介紹了Go-客戶信息關(guān)系系統(tǒng)的實(shí)現(xiàn),本文章內(nèi)容詳細(xì),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,需要的朋友可以參考下2023-01-01
Go語言 channel如何實(shí)現(xiàn)歸并排序中的merge函數(shù)詳解
這篇文章主要給大家介紹了關(guān)于Go語言 channel如何實(shí)現(xiàn)歸并排序中merge函數(shù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-02-02
GO中的時(shí)間操作總結(jié)(time&dateparse)
日常開發(fā)過程中,對(duì)于時(shí)間的操作可謂是無處不在,但是想實(shí)現(xiàn)時(shí)間自由還是不簡(jiǎn)單的,多種時(shí)間格式容易混淆,本文為大家整理了一下GO中的時(shí)間操作,有需要的可以參考下2023-09-09
Golang使用WebSocket通信的實(shí)現(xiàn)
這篇文章主要介紹了Golang使用WebSocket通信的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
以alpine作為基礎(chǔ)鏡像構(gòu)建Golang可執(zhí)行程序操作
這篇文章主要介紹了以alpine作為基礎(chǔ)鏡像構(gòu)建Golang可執(zhí)行程序操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12
詳解Golang中errors包如何返回自定義error類型
這篇文章主要為大家詳細(xì)介紹了Golang中errors包如何返回自定義error類型,文中的示例代碼簡(jiǎn)潔易懂,有需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-09-09
十個(gè)Golang開發(fā)中應(yīng)該避免的錯(cuò)誤總結(jié)
Go是一種靜態(tài)類型的、并發(fā)的、垃圾收集的編程語言,由谷歌開發(fā)。開發(fā)人員在編寫Go代碼時(shí)總會(huì)有一些常見的錯(cuò)誤,下面是Go語言中需要避免的十大壞錯(cuò)誤,希望對(duì)大家有所幫助2023-03-03

