有趣的面試題:Go語言字符串的字節長度和字符個數

背景

哈嘍,你們好,我是 asong。今天咱們一塊兒來看看 Go語言中的 rune數據類型,首先從一道面試題入手,你能很快說出下面這道題的答案嗎?
func main()  {
    str := "Golang夢工廠"
    fmt.Println(len(str))
    fmt.Println(len([]rune(str)))
}

運行結果是1515仍是159呢?先思考一下,一會揭曉答案。前端

其實這並非一道面試題,是我在平常開發中遇到的一個問題,當時場景是這樣的:後端要對前端傳來的字符串作字符校驗,產品的需求是限制爲200字符,而後我在後端作校驗時直接使用len(str) > 200來作判斷,結果出現了bug,前端字符校驗沒有超過200字符,調用後端接口確一直是參數錯誤,改爲使用len([]rune(str)) > 200成功解決了這個問題。具體緣由咱們在文中揭曉。面試

Unicode和字符編碼

在介紹rune類型以前,咱們仍是要從一些基礎知識開始。 ------ Unicode和字符編碼。後端

  • 什麼是Unicode

咱們都知道計算機只能處理數字,若是想要處理文本須要轉換爲數字才能處理,早些時候,計算機在設計上採用8bit做爲一個byte,一個byte表示的最大整數就是255,想表示更大的整數,就須要更多的byte。顯然,一個字節表示中文,是不夠的,至少須要兩個字節,並且還不能和ASCII編碼衝突,因此,我國制定了GB2312編碼,用來把中文編進去。可是世界上有不少語言,不一樣語言制定一個編碼,就會不可避免地出現衝突,因此unicode字符就是來解決這個痛點的。Unicode把全部語言都統一到一套編碼裏。總結來講:"unicode其實就是對字符的一種編碼方式,能夠理解爲一個字符---數字的映射機制,利用一個數字便可表示一個字符。"數組

  • 什麼是字符編碼?

雖然unicode把全部語言統一到一套編碼裏了,可是他卻沒有規定字符對應的二進制碼是如何存儲。以漢字「漢」爲例,它的 Unicode 碼點是 0x6c49,對應的二進制數是 110110001001001,二進制數有 15 位,這也就說明了它至少須要 2 個字節來表示。能夠想象,在 Unicode 字典中日後的字符可能就須要 3 個字節或者 4 個字節,甚至更多字節來表示了。分佈式

這就致使了一些問題,計算機怎麼知道你這個 2 個字節表示的是一個字符,而不是分別表示兩個字符呢?這裏咱們可能會想到,那就取個最大的,假如 Unicode 中最大的字符用 4 字節就能夠表示了,那麼咱們就將全部的字符都用 4 個字節來表示,不夠的就往前面補 0。這樣確實能夠解決編碼問題,可是卻形成了空間的極大浪費,若是是一個英文文檔,那文件大小就大出了 3 倍,這顯然是沒法接受的。函數

因而,爲了較好的解決 Unicode 的編碼問題, UTF-8 UTF-16 兩種當前比較流行的編碼方式誕生了。UTF-8 是目前互聯網上使用最普遍的一種 Unicode 編碼方式,它的最大特色就是可變長。它可使用 1 - 4 個字節表示一個字符,根據字符的不一樣變換長度。在UTF-8編碼中,一個英文爲一個字節,一箇中文爲三個字節。學習

Go語言中的字符串

基本概念

先來看一下官方對string的定義:ui

// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
type string string

人工翻譯:編碼

string8位字節的集合,一般但不必定表明 UTF-8編碼的文本。 string能夠爲空,但不能爲 nilstring的值是不能改變的

說得通俗一點,其實字符串其實是隻讀的字節切片,對於字符串底層而言就是一個byte數組,不過這個數組是隻讀的,不容許修改。spa

寫個例子驗證一下:

func main()  {
    byte1 := []byte("Hl Asong!")
    byte1[1] = 'i'

    str1 := "Hl Asong!"
    str1[1] = 'i'
}

對於byte的操做是可行,而string操做會直接報錯:

cannot assign to str1[1]

因此說string修改操做是不容許的,僅僅支持替換操做。

根據前面的分析,咱們也能夠得出咱們將字符存儲在字符串中時,也就是按字節進行存儲的,因此最後存儲的實際上是一個數值。

Go語言的字符串編碼

上面咱們介紹了字符串的基本概念,接下來咱們看一下Go語言中的字符串編碼是怎樣的。

Go 源代碼爲 UTF-8 編碼格式的,源代碼中的字符串直接量是 UTF-8 文本。因此Go語言中字符串是UTF-8編碼格式的。

Go語言字符串循環

Go語言中字符串可使用range循環和下標循環。咱們寫一個例子,看一下兩種方式循環有什麼區別:

func main()  {
    str := "Golang夢工廠"
    for k,v := range str{
        fmt.Printf("v type: %T index,val: %v,%v \n",v,k,v)
    }
    for i:=0 ; i< len(str) ; i++{
        fmt.Printf("v type: %T index,val:%v,%v \n",str[i],i,str[i])
    }
}

運行結果:

v type: int32 index,val: 0,71 
v type: int32 index,val: 1,111 
v type: int32 index,val: 2,108 
v type: int32 index,val: 3,97 
v type: int32 index,val: 4,110 
v type: int32 index,val: 5,103 
v type: int32 index,val: 6,26790 
v type: int32 index,val: 9,24037 
v type: int32 index,val: 12,21378 
v type: uint8 index,val:0,71 
v type: uint8 index,val:1,111 
v type: uint8 index,val:2,108 
v type: uint8 index,val:3,97 
v type: uint8 index,val:4,110 
v type: uint8 index,val:5,103 
v type: uint8 index,val:6,230 
v type: uint8 index,val:7,162 
v type: uint8 index,val:8,166 
v type: uint8 index,val:9,229 
v type: uint8 index,val:10,183 
v type: uint8 index,val:11,165 
v type: uint8 index,val:12,229 
v type: uint8 index,val:13,142 
v type: uint8 index,val:14,130

根據運行結果咱們能夠得出以下結論:

使用下標遍歷獲取的是 ASCII字符,而使用 Range遍歷獲取的是 Unicode字符。

什麼是rune數據類型

官方對rune的定義以下:

// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32

人工翻譯:

runeint32的別名,在全部方面都等同於 int32,按照約定,它用於區分字符值和整數值。

說的通俗一點就是rune一個值表明的就是一個Unicode字符,由於一個Go語言中字符串編碼爲UTF-8,使用1-4字節就能夠表示一個字符,因此使用int32類型範圍就能夠完美適配。

答案揭曉

前面說了這麼多知識點,確實有點亂了,咱們如今就根據開始的那道題來作一個總結。爲了方便查看,在貼一下這道題:

func main()  {
    str := "Golang夢工廠"
    fmt.Println(len(str))
    fmt.Println(len([]rune(str)))
}

這道題的正確答案是159

具體緣由:

len()函數是用來獲取字符串的字節長度, rune一個值表明的就是一個 Unicode字符,因此求 rune切片的長度就是字符個數。由於在 utf-8編碼中,英文佔 1個字節,中文佔 3個字節,因此最終結果就是 159

貼個圖,方便理解:

unicode/utf8

若是你們對rune的使用不是很明確,能夠學習使用一下Go標準庫unicode/utf8,其中提供了多種關於rune的使用方法。好比上面這道題,咱們就可使用utf8.RuneCountInString方法獲取字符個數。更多庫函數使用方法請自行解鎖,本篇就不作過多介紹了。

總結

針對全文,咱們作一個總結:

  • Go語言源代碼始終爲UTF-8
  • Go語言的字符串能夠包含任意字節,字符底層是一個只讀的byte數組。
  • Go語言中字符串能夠進行循環,使用下表循環獲取的acsii字符,使用range循環獲取的unicode字符。
  • Go語言中提供了rune類型用來區分字符值和整數值,一個值表明的就是一個Unicode字符。
  • Go語言中獲取字符串的字節長度使用len()函數,獲取字符串的字符個數使用utf8.RuneCountInString函數或者轉換爲rune切片求其長度,這兩種方法均可以達到預期結果。

好啦,這篇文章就到這裏啦,素質三連(分享、點贊、在看)都是筆者持續創做更多優質內容的動力!

建立了一個Golang學習交流羣,歡迎各位大佬們踊躍入羣,咱們一塊兒學習交流。入羣方式:關注公衆號獲取。更多學習資料請到公衆號領取。

我是asong,一名普普統統的程序猿,讓咱們一塊兒慢慢變強吧。歡迎各位的關注,咱們下期見~~~

推薦往期文章:

相關文章
相關標籤/搜索