mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-12-19 20:24:20 +08:00
ch3 review
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
### 3.5.3. UTF-8
|
||||
|
||||
|
||||
UTF8是一個將Unicode碼點編碼爲字節序列的變長編碼. UTF8編碼由Go語言之父 Ken Thompson 和 Rob Pike 共同發明, 現在已經是Unicode的標準. UTF8使用1到4個字節來表示每個Unicode碼點符號, ASCII部分字符隻使用1個字節, 常用字符部分使用2或3個字節. 每個符號編碼後第一個字節的高端bit位用於表示總共有多少個字節. 如果第一個字節的高端bit爲0, 則表示對應7bit的ASCII字符, 每個字符一個字節, 和傳統的ASCII編碼兼容. 如果第一個字節的高端bit是110, 則説明需要2個字節; 後續的每個高端bit都以10開頭. 更大的Unicode碼點也是采用類似的策略處理.
|
||||
UTF8是一個將Unicode碼點編碼爲字節序列的變長編碼。UTF8編碼由Go語言之父Ken Thompson和Rob Pike共同發明的,現在已經是Unicode的標準。UTF8编码使用1到4個字節來表示每個Unicode碼點,ASCII部分字符隻使用1個字節,常用字符部分使用2或3個字節表示。每個符號編碼後第一個字節的高端bit位用於表示總共有多少编码個字節。如果第一個字節的高端bit爲0,則表示對應7bit的ASCII字符,ASCII字符每個字符依然是一個字節,和傳統的ASCII編碼兼容。如果第一個字節的高端bit是110,則説明需要2個字節;後續的每個高端bit都以10開頭。更大的Unicode碼點也是采用類似的策略處理。
|
||||
|
||||
```
|
||||
0xxxxxxx runes 0-127 (ASCII)
|
||||
@@ -10,11 +9,11 @@ UTF8是一個將Unicode碼點編碼爲字節序列的變長編碼. UTF8編碼由
|
||||
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-0x10ffff (other values unused)
|
||||
```
|
||||
|
||||
變長的編碼無法直接通過索引來訪問第n個字符, 但是UTF8穫得了很多額外的優點. 首先UTF8編碼比較緊湊, 兼容ASCII, 併且可以自動同步: 它可以通過向前迴朔最多2個字節就能確定當前字符編碼的開始字節的位置. 它也是一個前綴編碼, 所以當從左向右解碼時不會有任何歧義也併不需要向前査看. 沒有任何字符的編碼是其它字符編碼的子串, 或是其它編碼序列的字串, 因此蒐索一個字符時隻要蒐索它的字節編碼序列卽可, 不用擔心前後的上下文會對蒐索結果産生榦擾. 同時UTF8編碼的順序和Unicode碼點的順序一致, 因此可以直接排序UTF8編碼序列. 同業也沒有嵌入的NUL(0)字節, 可以很好地兼容那些使用NUL作爲字符串結尾的編程語言.
|
||||
變長的編碼無法直接通過索引來訪問第n個字符,但是UTF8编码穫得了很多額外的優點。首先UTF8編碼比較緊湊,完全兼容ASCII码,併且可以自動同步:它可以通過向前迴朔最多2個字節就能確定當前字符編碼的開始字節的位置。它也是一個前綴編碼,所以當從左向右解碼時不會有任何歧義也併不需要向前査看(译注:像GBK之类的编码,如果不知道起点位置则可能会出现歧义)。沒有任何字符的編碼是其它字符編碼的子串,或是其它編碼序列的字串,因此蒐索一個字符時隻要蒐索它的字節編碼序列卽可,不用擔心前後的上下文會對蒐索結果産生榦擾。同時UTF8編碼的順序和Unicode碼點的順序一致,因此可以直接排序UTF8編碼序列。同时因为沒有嵌入的NUL(0)字節,可以很好地兼容那些使用NUL作爲字符串結尾的編程語言。
|
||||
|
||||
Go的源文件采用UTF8編碼, 併且Go處理UTF8編碼的文本也很出色. unicode 包提供了諸多處理 rune 字符相關功能的函數函數(區分字母和數組, 或者是字母的大寫和小寫轉換等), unicode/utf8 包了提供了rune 字符序列的UTF8編碼和解碼的功能.
|
||||
Go语言的源文件采用UTF8編碼,併且Go语言處理UTF8編碼的文本也很出色。unicode包提供了諸多處理rune字符相關功能的函數(比如區分字母和數組,或者是字母的大寫和小寫轉換等),unicode/utf8包则提供了用于rune字符序列的UTF8編碼和解碼的功能。
|
||||
|
||||
有很多Unicode字符很難直接從鍵盤輸入, 併且很多字符有着相似的結構; 有一些甚至是不可見的字符. Go字符串面值中的Unicode轉義字符讓我們可以通過Unicode碼點輸入特殊的字符. 有兩種形式, \uhhhh 對應16bit的碼點值, \Uhhhhhhhh 對應32bit的碼點值, 其中h是一個十六進製數字; 一般很少需要使用32bit的形式. 每一個對應碼點的UTF8編碼. 例如: 下面的字母串面值都表示相同的值:
|
||||
有很多Unicode字符很難直接從鍵盤輸入,併且还有很多字符有着相似的結構;有一些甚至是不可見的字符(译注:中文和日文就有很多相似但不同的字)。Go语言字符串面值中的Unicode轉義字符讓我們可以通過Unicode碼點輸入特殊的字符。有兩種形式:\uhhhh對應16bit的碼點值,\Uhhhhhhhh對應32bit的碼點值,其中h是一個十六進製數字;一般很少需要使用32bit的形式。每一個對應碼點的UTF8編碼。例如:下面的字母串面值都表示相同的值:
|
||||
|
||||
```
|
||||
"世界"
|
||||
@@ -23,17 +22,17 @@ Go的源文件采用UTF8編碼, 併且Go處理UTF8編碼的文本也很出色. u
|
||||
"\U00004e16\U0000754c"
|
||||
```
|
||||
|
||||
上面三個轉義序列爲第一個字符串提供替代寫法, 但是它們的值都是相同的.
|
||||
上面三個轉義序列都爲第一個字符串提供替代寫法,但是它們的值都是相同的。
|
||||
|
||||
Unicode轉義也可以使用在rune字符中. 下面三個字符是等價的:
|
||||
Unicode轉義也可以使用在rune字符中。下面三個字符是等價的:
|
||||
|
||||
```
|
||||
'世' '\u4e16' '\U00004e16'
|
||||
```
|
||||
|
||||
對於小於256碼點值可以寫在一個十六進製轉義字節中, 例如 '\x41' 對應 'A' 字符, 但是對於更大的碼點則必鬚使用 \u 或 \U 轉義形式. 因此, '\xe4\xb8\x96' 併不是一個合法的rune字符, 雖然這三個字節對應一個有效的UTF8編碼的碼點.
|
||||
對於小於256碼點值可以寫在一個十六進製轉義字節中,例如'\x41'對應字符'A',但是對於更大的碼點則必鬚使用\u或\U轉義形式。因此,'\xe4\xb8\x96'併不是一個合法的rune字符,雖然這三個字節對應一個有效的UTF8編碼的碼點。
|
||||
|
||||
得意於UTF8優良的設計, 諸多字符串操作都不需要解碼. 我們可以不用解碼直接測試一個字符串是否是另一個字符串的前綴:
|
||||
得益於UTF8编码優良的設計,諸多字符串操作都不需要解碼操作。我們可以不用解碼直接測試一個字符串是否是另一個字符串的前綴:
|
||||
|
||||
```Go
|
||||
func HasPrefix(s, prefix string) bool {
|
||||
@@ -41,7 +40,7 @@ func HasPrefix(s, prefix string) bool {
|
||||
}
|
||||
```
|
||||
|
||||
或者是後綴測試:
|
||||
或者是後綴測試:
|
||||
|
||||
```Go
|
||||
func HasSuffix(s, suffix string) bool {
|
||||
@@ -49,7 +48,7 @@ func HasSuffix(s, suffix string) bool {
|
||||
}
|
||||
```
|
||||
|
||||
或者是包含子串測試:
|
||||
或者是包含子串測試:
|
||||
|
||||
```Go
|
||||
func Contains(s, substr string) bool {
|
||||
@@ -62,9 +61,9 @@ func Contains(s, substr string) bool {
|
||||
}
|
||||
```
|
||||
|
||||
對於UTF8編碼後文本的處理和原始的字節處理邏輯一樣. 但是對應很多其它編碼則併不是這樣的. (上面的函數都來自 strings 字符串處理包, 雖然它們的實現包含了一個用哈希技術優化的 Contains 實現.)
|
||||
對於UTF8編碼後文本的處理和原始的字節處理邏輯是一樣的。但是對應很多其它編碼則併不是這樣的。(上面的函數都來自strings字符串處理包,真实的代码包含了一個用哈希技術優化的Contains 實現。)
|
||||
|
||||
另以方面, 如果我們眞的關心每個Unicode字符, 我們可以使用其它機製. 考慮前面的第一個例子中的字符串, 它包混合了中西兩種字符. 圖3.5展示了它的內存表示形式. 字符串包含13個字節, 以UTF8形式編碼, 但是隻對應9個Unicode字符:
|
||||
另一方面,如果我們眞的關心每個Unicode字符,我們可以使用其它处理方式。考慮前面的第一個例子中的字符串,它包混合了中西兩種字符。圖3.5展示了它的內存表示形式。字符串包含13個字節,以UTF8形式編碼,但是隻對應9個Unicode字符:
|
||||
|
||||
```Go
|
||||
import "unicode/utf8"
|
||||
@@ -74,7 +73,7 @@ fmt.Println(len(s)) // "13"
|
||||
fmt.Println(utf8.RuneCountInString(s)) // "9"
|
||||
```
|
||||
|
||||
爲了處理這些眞實的字符, 我們需要一個UTF8解碼器. unicode/utf8 包提供了實現, 我們可以這樣使用:
|
||||
爲了處理這些眞實的字符,我們需要一個UTF8解碼器。unicode/utf8包提供了该功能,我們可以這樣使用:
|
||||
|
||||
```Go
|
||||
for i := 0; i < len(s); {
|
||||
@@ -84,7 +83,7 @@ for i := 0; i < len(s); {
|
||||
}
|
||||
```
|
||||
|
||||
每一次調用 DecodeRuneInString 函數都返迴一個 r 和 長度, r 對應字符本身, 長度對應r采用UTF8編碼後的字節數目. 長度可以用於更新第i個字符在字符串中的字節索引位置. 但是這種方式是笨拙的, 我們需要更簡潔的語法. 幸運的是, Go的range循環在處理字符串的時候, 會自動隱式解碼UTF8字符串. 下面的循環運行如圖3.5所示; 需要註意的是對於非ASCII, 索引更新的步長超過1個字節.
|
||||
每一次調用DecodeRuneInString函數都返迴一個r和長度,r對應字符本身,長度對應r采用UTF8編碼後的编码字節數目。長度可以用於更新第i個字符在字符串中的字節索引位置。但是這種编码方式是笨拙的,我們需要更簡潔的語法。幸運的是,Go语言的range循環在處理字符串的時候,會自動隱式解碼UTF8字符串。下面的循環運行如圖3.5所示;需要註意的是對於非ASCII,索引更新的步長将超過1個字節。
|
||||
|
||||

|
||||
|
||||
@@ -94,7 +93,7 @@ for i, r := range "Hello, 世界" {
|
||||
}
|
||||
```
|
||||
|
||||
我們可以使用一個簡單的循環來統計字符串中字符的數目, 像這樣:
|
||||
我們可以使用一個簡單的循環來統計字符串中字符的數目,像這樣:
|
||||
|
||||
```Go
|
||||
n := 0
|
||||
@@ -103,7 +102,7 @@ for _, _ = range s {
|
||||
}
|
||||
```
|
||||
|
||||
想其它形式的循環那樣, 我們可以忽略不需要的變量:
|
||||
像其它形式的循環那樣,我們也可以忽略不需要的變量:
|
||||
|
||||
```Go
|
||||
n := 0
|
||||
@@ -112,15 +111,15 @@ for range s {
|
||||
}
|
||||
```
|
||||
|
||||
或者我們可以直接調用 utf8.RuneCountInString(s) 函數.
|
||||
或者我們可以直接調用utf8.RuneCountInString(s)函數。
|
||||
|
||||
正如我們前面提到了, 文本字符串采用UTF8編碼隻是一種慣例,但是對於循環的眞正字符串併不是一個慣例, 這是正確的. 如果用於循環的字符串隻是一個普通的二進製數據, 或者是含有錯誤編碼的UTF8數據, 將會發送什麽?
|
||||
正如我們前面提到的,文本字符串采用UTF8編碼隻是一種慣例,但是對於循環的眞正字符串併不是一個慣例,這是正確的。如果用於循環的字符串隻是一個普通的二進製數據,或者是含有錯誤編碼的UTF8數據,將會發送什麽呢?
|
||||
|
||||
每一個UTF8字符解碼, 不管是顯示地調用 utf8.DecodeRuneInString 解碼或在 range 循環中隱式地解碼, 如果遇到一個錯誤的輸入字節, 將生成一個特别的Unicode字符 '\uFFFD', 在印刷中這個符號通常是一個黑色六角或鑽石形狀, 里面包含一個白色的問號(?). 當程序遇到這樣的一個字符, 通常是一個信號, 説明輸入併不是一個完美沒有錯誤的的UTF8編碼字符串.
|
||||
每一個UTF8字符解碼,不管是顯式地調用utf8.DecodeRuneInString解碼或是在range循環中隱式地解碼,如果遇到一個錯誤的UTF8编码輸入,將生成一個特别的Unicode字符'\uFFFD',在印刷中這個符號通常是一個黑色六角或鑽石形狀,里面包含一個白色的問號(?)。當程序遇到這樣的一個字符,通常是一個危险信號,説明輸入併不是一個完美沒有錯誤的UTF8字符串。
|
||||
|
||||
UTF8作爲交換格式是非常方便的, 但是在程序內部采用rune類型可能更方便, 因爲rune大小一致, 支持數組索引和方便切割.
|
||||
UTF8字符串作爲交換格式是非常方便的,但是在程序內部采用rune序列可能更方便,因爲rune大小一致,支持數組索引和方便切割。
|
||||
|
||||
string 接受到 []rune 的轉換, 可以將一個UTF8編碼的字符串解碼爲Unicode字符序列:
|
||||
string接受到[]rune的类型轉換,可以將一個UTF8編碼的字符串解碼爲Unicode字符序列:
|
||||
|
||||
```Go
|
||||
// "program" in Japanese katakana
|
||||
@@ -130,22 +129,22 @@ r := []rune(s)
|
||||
fmt.Printf("%x\n", r) // "[30d7 30ed 30b0 30e9 30e0]"
|
||||
```
|
||||
|
||||
(在第一個Printf中的 `% x` 參數用於在每個十六進製數字前插入一個空格.)
|
||||
(在第一個Printf中的`% x`參數用於在每個十六進製數字前插入一個空格。)
|
||||
|
||||
如果是將一個 []rune 類型的Unicode字符切片或數組轉爲string, 則對它們進行UTF8編碼:
|
||||
如果是將一個[]rune類型的Unicode字符slice或數組轉爲string,則對它們進行UTF8編碼:
|
||||
|
||||
```Go
|
||||
fmt.Println(string(r)) // "プログラム"
|
||||
```
|
||||
|
||||
將一個整數轉型爲字符串意思是生成整數作爲Unicode碼點的UTF8編碼的字符串:
|
||||
將一個整數轉型爲字符串意思是生成以只包含对应Unicode碼點字符的UTF8字符串:
|
||||
|
||||
```Go
|
||||
fmt.Println(string(65)) // "A", not "65"
|
||||
fmt.Println(string(0x4eac)) // "京"
|
||||
```
|
||||
|
||||
如果對應碼點的字符是無效的, 則用'\uFFFD'無效字符作爲替換:
|
||||
如果對應碼點的字符是無效的,則用'\uFFFD'無效字符作爲替換:
|
||||
|
||||
```Go
|
||||
fmt.Println(string(1234567)) // "(?)"
|
||||
|
||||
Reference in New Issue
Block a user