引言
以前用 EBCDIC 和 ASCII 編碼蛙卤,(別看只有兩種編碼)粘姜,但事情從來沒有簡(jiǎn)單過盗温,恰恰相反變得越來越復(fù)雜了。但據(jù)推測(cè),編碼簡(jiǎn)化就像(黎明前)地平線上閃過了一道光缝左,但要等到天亮還得 50 年亿遂。
早期計(jì)算機(jī)是從美國(guó)浓若、英國(guó)、澳大利亞這些英語(yǔ)國(guó)家發(fā)展起來的蛇数,結(jié)果計(jì)算機(jī)字符集就以這些國(guó)家使用的語(yǔ)言和字符進(jìn)行設(shè)計(jì)挪钓,大體上,也就是拉丁字母
苞慢,加上數(shù)字
诵原、標(biāo)點(diǎn)
和別的字符
。他們使用 ASCII 或 EBCDIC 進(jìn)行編碼挽放。
字符處理的機(jī)制是基于此的:文本文件和基于字節(jié)序列的基本輸入輸出绍赛,每個(gè)字節(jié)代表一個(gè)單獨(dú)的字符。字符串比較可以通過對(duì)比相對(duì)應(yīng)的字節(jié)實(shí)現(xiàn)辑畦,字符串的大小寫轉(zhuǎn)換可以通過單個(gè)字節(jié)的操作完成吗蚌,等等。
用理工科的眼光看纯出,世界上只有 ASCII 一種編碼就清靜了蚯妇。但實(shí)際正是相反的趨勢(shì),越來越多的人需要計(jì)算機(jī)軟件中使用自己熟悉的語(yǔ)言暂筝。如果你的軟件可以在不同的國(guó)家運(yùn)行箩言,那你的用戶就需要軟件使用他們自己的語(yǔ)言。在分布式的系統(tǒng)中焕襟,使用不同的系統(tǒng)模塊的人可能希望不同的語(yǔ)言和字符陨收。
國(guó)際化(i18n)
是指你的應(yīng)用怎么處理不同的語(yǔ)言和文化。本地化(l10n)
是說你怎么把國(guó)際化的應(yīng)用適配成小群體使用鸵赖。
國(guó)際化和本地化各自都是一個(gè)很大的課題务漩。舉個(gè)例子,關(guān)于顏色的話題:白色在西方表示純潔它褪,在中國(guó)表示死亡饵骨,在埃及表示喜悅。在這章中我們只關(guān)注字符的處理茫打。
定義
我們所關(guān)心的是系統(tǒng)處理你所表述的內(nèi)容居触,十分重要。下面是有人做的一套行之有效的定義方法老赤。
字符
字符
是"自然語(yǔ)言中用符號(hào)表示信息的單位轮洋,比如字母、數(shù)字诗越、標(biāo)點(diǎn)"(維基百科)砖瞧,字符是有價(jià)值的最小書寫單位(Unicode)這就包括了 a 和 A,或其他語(yǔ)言字符嚷狞,也包括數(shù)字 2和標(biāo)點(diǎn)','块促,還有像 '£'這樣的字符荣堰。
字符實(shí)際上是符號(hào)的抽象組合,也就是說 a 代表了所有手寫的 a竭翠,有點(diǎn)像柏拉圖圓也是圓的關(guān)系振坚。原則上字符也包括控制字符,也就是實(shí)際中不存在只是為了處理語(yǔ)言的格式用的斋扰。
字符本身并不沒有特定形狀渡八,只是我們通過形狀來識(shí)別它。即使如此传货,我們也要聯(lián)系上下文才能理解:數(shù)學(xué)中屎鳍,如果你看到 π (pi)這個(gè)字符,它表示圓周率问裕,但是如果你讀希臘文逮壁,它只是 16 個(gè)字母;"προσ"是希臘詞語(yǔ)“with”粮宛,這個(gè)和 3.14159 沒有半點(diǎn)關(guān)系窥淆。
字符體系和字符集
字符集
就是一個(gè)不同的且唯一的字符的集合,像拉丁字母巍杈,不需要指定順序忧饭。在英語(yǔ)中,盡管我們說 a 是在 z 的前面筷畦,但我們不說 a 比 z 要小词裤。電話聯(lián)系人的排序方式里,McPhee 在MacRea 的前面說明了字母排序不是嚴(yán)格的按字符的順序汁咏。
字符體系
就是字名和字形的結(jié)合亚斋,比如作媚,a 可能寫成 'a', 'a' or 'a'攘滩,但這不是強(qiáng)制的,他們只是樣本纸泡。字符體系可能區(qū)分大小寫漂问,所以 a 和 A 是不同的。但他們的意思可能是一樣的女揭,就算是長(zhǎng)的不一樣蚤假。(有點(diǎn)像編程語(yǔ)言對(duì)待大小寫,有的大小寫敏感吧兔,比如 Go 語(yǔ)言磷仰,有的就是一樣的,比如 Basic境蔼。)灶平。另一方面伺通,字符系統(tǒng)可能包括長(zhǎng)的一樣但意義不同的:希臘字母的數(shù)學(xué)符號(hào)就有兩個(gè)意思,比如 π逢享。他們也被叫成無法編碼的字符集罐监。
字符編碼
字符編碼
是字符到整數(shù)的映射。一個(gè)字符集的映射也被稱為一個(gè)編碼字符集
或字符集
瞒爬。這個(gè)映射中的每個(gè)字符的值通常被稱為一個(gè)編碼(code point)弓柱。 ASCII 也是一個(gè)字符集,'a'的編碼是 97侧但,'A'是 65(十進(jìn)制)矢空。
字符編碼仍然是一個(gè)抽象的概念。它不是我們可以看到的文件或者 TCP 的包禀横。不過妇多,確和這兩個(gè)概念很像,它就是一種把人抽象出來的概念轉(zhuǎn)化為數(shù)字的映射關(guān)系燕侠。
字符編碼
字符的交互(傳輸)和存儲(chǔ)都要以某種方式編碼者祖。要發(fā)送一個(gè)字符串,你需要將字符串中的所有字符進(jìn)行編碼绢彤。每種字符集都有很多的編碼方案七问。
例如,7 位字節(jié) ASCII 編碼可以轉(zhuǎn)換成 8 位字節(jié)(8 進(jìn)制)茫舶。所以械巡,ASCII 的'A'(編碼值 65)可以被編碼為 8 進(jìn)制的 01000001。不過饶氏,另一種不同的編碼方式對(duì)最高位別有用途讥耗,如奇偶校驗(yàn),帶有奇校驗(yàn)的 ASCII 編碼“A”將是這個(gè) 8 進(jìn)制數(shù)11000001疹启。還有一些協(xié)議古程,如 Sun的 XDR,使用 32 位字長(zhǎng)編碼 ASCII 編碼喊崖。所以挣磨,'A'將被編碼為0000000000000000000000001000001。
字符編碼是在程序應(yīng)用層面使用的荤懂。應(yīng)用程序處理編碼的字符時(shí)茁裙,是否帶包含奇偶校驗(yàn)處理8 位字符或 32 位字符,顯然有很大的差別节仿。
把字符編碼擴(kuò)展到字符串晤锥。一個(gè)字節(jié)寬、帶有奇偶校驗(yàn)的“ABC”編碼為 10000000(高位奇偶校驗(yàn))0100000011(C)01000010(B)01000001(A 在低位)廊宪。對(duì)于編碼在字符串上的討論也很重要矾瘾,雖然編碼規(guī)則可能不同眉踱。
編碼傳輸
某個(gè)應(yīng)用程序的字符編碼只要內(nèi)部能處理字符串就足夠了。然而霜威,一旦你需要在不同應(yīng)用程序之間交互谈喳,那怎么編碼可就成了需要進(jìn)一步討論問題了:字節(jié)、字符戈泼、字是怎么傳輸?shù)男銮荨W址幋a可能有很多空白字符(待商議),從而可以使用如 zip 算法對(duì)文本進(jìn)行壓縮大猛,從而節(jié)省帶寬陨享”菜或者炬称,它可以減少到 7 位字節(jié)堰塌,奇偶校驗(yàn)位,使用 base64 編碼來代替唉堪。
如果我們知道的字符編碼和傳輸編碼模聋,那么問題就成了如何通過編程處理字符和字符串;如果我們不知道字符編碼和傳輸編碼唠亚,那么如何猜到某個(gè)特定字符串的編碼方式就是大問題链方。因?yàn)闆]有約定發(fā)送文件的字符編碼
不過,在互聯(lián)網(wǎng)上傳輸文本的編碼是有約定的灶搜。很簡(jiǎn)單:文本消息頭包含的編碼信息祟蚀。例如,HTTP 報(bào)頭可以包含這么幾行割卖,如
Content-Type: text/html; charset=ISO-8859-4
Content-Encoding: gzip
上面是說前酿,將字符集是 ISO 8859-4(對(duì)應(yīng)到歐洲的某些國(guó)家)作為默認(rèn)編碼,然后用 gzip壓縮鹏溯。內(nèi)容類型的第二部分就是我們指的是“傳輸編碼”(IETF RFC2130)罢维。
但是,怎么讀懂這個(gè)信息呢剿涮?它沒有編碼言津?這不就是先有雞還是先有蛋的問題么攻人?嗯取试,不是的。按照慣例怀吻,這樣的信息使用 ASCII 編碼(準(zhǔn)確地說瞬浓,美國(guó) ASCII),所以程序可以讀取headers蓬坡,然后適配其文檔的其余部分的編碼猿棉。
ASCII 編碼
ASCII 字符集包含的英文字符
磅叛、數(shù)字
,標(biāo)點(diǎn)符號(hào)
和一些控制字符
萨赁。
最常見的** ASCII 編碼使用 7 位字節(jié)**弊琴,所以 A 的碼是 65。
這個(gè)字符集是實(shí)際的美國(guó) ASCII杖爽。鑒于歐洲需要處理重音字符敲董,于是省略一些標(biāo)點(diǎn)字符,形成一個(gè)最小的字符集慰安,ISO 646腋寨,同時(shí)有合適的歐洲本國(guó)字符的“國(guó)家變種字符集”。有興趣的可以看看 Jukka Korpel 的這個(gè)網(wǎng)頁(yè) http://www.cs.tut.fi/?jkorpela/ chars.html化焕。
ISO 8859 字符集
8 進(jìn)制是字節(jié)的標(biāo)準(zhǔn)長(zhǎng)度萄窜。這使得 ASCII 可以有 128 個(gè)額外的編碼。 ISO 8859 系列的字符集可以包含眾多的歐洲語(yǔ)言字符集撒桨。查刻。 ISO 8859-1 也被稱為 Latin-1
,覆蓋了許多在西歐國(guó)家的語(yǔ)言凤类,同時(shí)這一系列的其他字符集包括歐洲其他國(guó)家赖阻,甚至希伯來語(yǔ),阿拉伯語(yǔ)和泰語(yǔ)踱蠢。例如火欧,ISO 8859-5 包括使用斯拉夫語(yǔ)字符的俄羅斯等,而 ISO 8859-8 則包含希伯來文字母茎截。
這些字符集使用 8進(jìn)制作
為標(biāo)準(zhǔn)的編碼格式苇侵。例如,在 ISO 8859-1 字符' 'á'的字符編碼為 193企锌,同時(shí)被編碼為 193榆浓。所有的 ISO 8859 系列前 128 個(gè)保持和 ASCII 相同的值,所以撕攒,ASCII 字符在所有這些集合都是相同的陡鹃。
HTML 語(yǔ)言規(guī)范
曾經(jīng)推薦 ISO 8859-1 字符集,不過 HTML3.2 之后的規(guī)范就不再推薦抖坪,4.0 開始推薦 Unicode 編碼萍鲸。2010 年 Google 通過它抓取的網(wǎng)頁(yè)做出了一個(gè)估算,20%的網(wǎng)頁(yè)使用ISO 8859 編碼擦俐,20%使用 ASCII(unicode 接近 50%脊阴,
Unicode 編碼
ASCII 和 ISO 8859 都不能覆蓋象形文字。中文大約有 20000 個(gè)獨(dú)立的字符,其中 5000 個(gè)常用字符嘿期。這些字符需要不止一個(gè)字節(jié)品擎,基本上雙字節(jié)都會(huì)被用上。也有一些多字節(jié)的編碼:中文的 Big5, EUC-TW, GB2312 和 GBK/GBX备徐,日文的 JIS X 0208萄传,等等。這些編碼通常是不兼容的
Unincode
是一個(gè)受到擁護(hù)的字符集編碼標(biāo)準(zhǔn)蜜猾,旨在統(tǒng)一主要使用的編碼盲再。它包含了歐洲文字、亞洲文字和印度文字等“晗常現(xiàn)在 Unicode 已經(jīng)到了 5.2 的版本答朋,包含 107,0000 個(gè)字符。編碼字符超過 65536棠笑,也就是 2^16梦碗。這已經(jīng)覆蓋了整個(gè)編碼。
(Unicode 編碼)前 256 個(gè)編碼對(duì)應(yīng) ISO 8859-1蓖救,同時(shí)前 128 個(gè)也是美式 ASCII 編碼洪规。所以主流的編碼都是相互兼容的,ISO 8859-1循捺、ASCII 和 Unicode 是一樣的斩例。對(duì)其他字符集則不一定正確:例如,雖然 Big5 編碼也在 Unicode 中从橘,但他們的編碼值并不相同念赶。http://moztw.org/docs/big5/table/unicode1.1-obsolete.txt 這個(gè)頁(yè)面就是證明:一張 Big5 到Unicode 的大的映射表。
為了在計(jì)算機(jī)系統(tǒng)中表示 Unicode 字符恰力,必須使用一個(gè)編碼方案叉谜。UCS 編碼使用兩個(gè)字節(jié)來編碼一個(gè)字符值。然而踩萎,Unicode 現(xiàn)在有太多的字符需要對(duì)應(yīng)到雙字節(jié)的編碼停局。以下方案是替代原來陳舊的編碼方案的:
- UTF-32 使用 4 個(gè)字節(jié)編碼,但是已經(jīng)
不再推薦
香府,HTML5 甚至嚴(yán)重警告反對(duì)使用 - UTF-16 是最常見的董栽,它通過溢出兩個(gè)字節(jié)來處理 ASCII 和 ISO 8859-1 外的字符
- UTF-8 每個(gè)字符使用 1 到 4 個(gè)字節(jié),所以 ASCII 值不變企孩,但 ISO 8859-1 的值會(huì)變化
- UTF-7 有時(shí)會(huì)用到锭碳,但
不常見
UTF-8, Go 語(yǔ)言和 runes
UTF - 8
是最常用的編碼。谷歌估計(jì)它抓取的網(wǎng)頁(yè)有 50%使用 UTF-8 編碼柠硕。ASCII 字符集具有相同的在 UTF-8 中編碼值相同工禾,所以 UTF-8 的讀取方法可以用 Unicode 字符集讀取一個(gè)ASCII 字符組成的網(wǎng)頁(yè)运提。
Go 語(yǔ)言使用 UTF-8 編碼字符串蝗柔。每個(gè)字符類型都是 rune
闻葵。rune 是 int32 的一個(gè)別名,因?yàn)閁nicode 編碼可以是 1,2 或 4 個(gè)字節(jié)癣丧。字符和字符串其實(shí)都是一個(gè) runes 的數(shù)組
Unicode 中一個(gè)字符串其實(shí)是一個(gè)字節(jié)數(shù)組槽畔,但是你要注意:只有 ASCII 這個(gè)字符集是一個(gè)字節(jié)等于一個(gè)字符。所有其他字符占用 2 個(gè)胁编,三個(gè)或四個(gè)字節(jié)厢钧。這意味著,一個(gè)字符串的長(zhǎng)度(runes)通常是不一樣的長(zhǎng)度的字節(jié)數(shù)組嬉橙。他們只有在全是 ASCII 字符是才相同早直。
下面的程序片段可以說明這些。如果我們使用 utf-8 來檢驗(yàn)它的長(zhǎng)度市框,你只會(huì)得到它字符層面的長(zhǎng)度霞扬。但如果你把字符串轉(zhuǎn)換成 rues 數(shù)組[]rune
,你就等到一個(gè) Unicode 編碼的數(shù)組:
str := "百度一下枫振,你就知道"
println("String length", len([]rune(str)))
println("Byte length", len(str))
輸出為
String length 9
Byte length 27
UTF-8 編碼的客戶端和服務(wù)端
可能令人驚訝的是喻圃,無論是客戶端或服務(wù)器你不需要對(duì) utf-8 的文本做任何特殊的處理。UTF-8 字符串的數(shù)據(jù)類型是一個(gè)字節(jié)數(shù)組粪滤,如上所示斧拍。Go 語(yǔ)言自動(dòng)處理編碼后的字符串是1,2杖小,3 或 4 個(gè)字節(jié)肆汹。所以 utf-8 的字符串你可以隨便寫。
類似于讀取字符串予权,只要讀入一個(gè)字節(jié)數(shù)組县踢,然后使用 string([]byte)將數(shù)組轉(zhuǎn)換成一個(gè)字符串。如果 Go 語(yǔ)言不能正確解碼伟件,將字節(jié)轉(zhuǎn)換為 Unicode 字符硼啤,那么它給使用 Unicode 替換字符\uFFFD。生成的字節(jié)數(shù)組的長(zhǎng)度是有效字符串的長(zhǎng)度斧账。
所以前面章節(jié)中提到的客戶端和服務(wù)端使用 uft-8 編碼表現(xiàn)的很好
ASCII 編碼的客戶端和服務(wù)器
ASCII 字符的 ASCII 編碼和 UTF-8 編碼的值相同谴返,所以普通的 UTF-8 字符能正常處理 ASCII字符,不需要做任何特殊的處理咧织。
Go 語(yǔ)言和 utf-16
utf-16 編碼
可以用 16 位字節(jié)無符號(hào)整形數(shù)組處理嗓袱。 utf16 包
就是用來處理這樣的字串的。將一個(gè) Go 語(yǔ)言的 utf-8 正常編碼的字串轉(zhuǎn)換 utf-16 的編碼习绢,你應(yīng)先將字串轉(zhuǎn)換成[]rune數(shù)組渠抹,然后使用 utf16.Encode 生成一個(gè) uint16 類型的數(shù)組蝙昙。
同樣,解碼一個(gè)無符號(hào)短整型的 utf-16 數(shù)組成一個(gè) Go 字符串梧却,你需要 utf16.Decode 將編碼轉(zhuǎn)換成[]rune 奇颠,然后才能改成一個(gè)字符串。如下面的代碼所示:
str := "百度一下放航,你就知道"
runes := utf16.Encode([]rune(str))
ints := utf16.Decode(runes)
str = string(ints)
類型轉(zhuǎn)換需要客戶端和服務(wù)器在合適的時(shí)機(jī)讀取和寫入 16 位的整數(shù)
Little-endian 和 big-endian
然而烈拒,UTF-16 編碼潛藏著一個(gè)小的惡魔。它基本上是一個(gè) 16 字節(jié)字符編碼广鳍。最大的問題是:每一個(gè)短字荆几,是如何拼寫的?高位在前還是高位在后赊时?無論哪種方式吨铸,只要是發(fā)生器和接收器約定好就可以
Unicode
通過一個(gè)特殊字節(jié)標(biāo)記了尋址方式,這個(gè)字節(jié)就被稱為 BOM(字節(jié)順序標(biāo)記)祖秒。這是一個(gè)零寬度非打印字符诞吱,所以你永遠(yuǎn)不會(huì)在文本中看到它。但是它通過 0xFFFE 的值狈涮,可以告訴你編碼的順序
- 在 big-endian 系統(tǒng)中狐胎,它是 FF FE
- 在 little-endian 系統(tǒng)中,它是 FE FF
有時(shí) BOM 會(huì)位于文本的第一個(gè)字符歌馍。文本被讀入時(shí)可以檢查握巢,以確定使用的是那種系統(tǒng)。
UTF-16 編碼的客戶端和服務(wù)器
根據(jù) BOM 的約定松却,服務(wù)器可以預(yù)先設(shè)置 BOM 來表示 utf-16,如下
/* UTF16 Server
*/
package main
import (
"fmt"
"net"
"os"
"unicode/utf16"
)
const BOM= '\ufffe'
func main() {
service := "0.0.0.0:1210"
tcpAddr, err := net.ResolveTCPAddr("tcp", service)
checkError(err)
listener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
for{
conn, err := listener.Accept()
if err != nil {
continue
}
str := "j'ai arrêté"
shorts := utf16.Encode([]rune(str))
writeShorts(conn, shorts)
conn.Close() // we're finished
}
}
func writeShorts(conn net.Conn, shorts []uint16) {
var bytes [2]byte
// send the BOM as first two bytes
bytes[0] = BOM>> 8
bytes[1] = BOM&255
_, err := conn.Write(bytes[0:])
if err != nil {
return
}
for _, v := range shorts {
bytes[0] = byte(v >> 8)
bytes[1] = byte(v & 255)
_, err = conn.Write(bytes[0:])
if err != nil {
return
}
}
}
func checkError(err error) {
if err != nil {
fmt.Println("Fatal error ", err.Error())
os.Exit(1)
}
}
但客戶端讀取一個(gè)字節(jié)流暴浦,提取并檢查 BOM 時(shí)解碼該流的其余部分的。
/* UTF16 Client
*/
package main
import (
"fmt"
"net"
"os"
"unicode/utf16"
)
const BOM= '\ufffe'
func main() {
if len(os.Args) != 2 {
fmt.Println("Usage: ", os.Args[0], "host:port")
os.Exit(1)
}
service := os.Args[1]
conn, err := net.Dial("tcp", service)
checkError(err)
shorts := readShorts(conn)
ints := utf16.Decode(shorts)
str := string(ints)
fmt.Println(str)
os.Exit(0)
}
func readShorts(conn net.Conn) []uint16{
var buf [512]byte
// read everything into the buffer
n, err := conn.Read(buf[0:2])
for true {
err := conn.Read(buf[n:])
if m == 0 || err != nil {
break
}
n += m
}
checkError(err)
var shorts []uint16
shorts = make([]uint16, n/2)
if buf[0] == 0xff && buf[1] == 0xfe {
// big endian
for i := 2; i <n; i += 2 {
shorts[i/2] = uint16(buf[i])<<8 + uint16(buf[i+1]) )<<8 + uint16(buf[i+1])
}
} else if buf[1] == 0xff && buf[0] == 0xfe {
// little endian
for i := 2; i < n; i += 2 {
shorts[i/2] = uint16(buf[i+1])<<8 + uint16(buf[i]) 1])<<8 + uint16(buf[i])
}
} else{
// unknown byte order
fmt.Println("Unknown order")
}
return shorts
}
func checkError(err error) {
if err != nil {
fmt.Println("Fatal error ", err.Error())
os.Exit(1)
}
}
Unicode 的疑難雜癥
這本書不是有關(guān)國(guó)際化問題晓锻。特別是歌焦,我們不想鉆研的神秘的 Unicode。但是你應(yīng)該知道砚哆,Unicode 不是一個(gè)簡(jiǎn)單的編碼独撇,也有很多的復(fù)雜的地方。例如躁锁,一些早期的字符集用非空格字符纷铣,尤其是重音字符。這些重音字符要轉(zhuǎn)換成 Unicode 可以用兩種辦法:作為一個(gè) Unicode字符战转,或作為一個(gè)非空格字符和非重音字符的組合搜立。例如, U+04D6 CYRILLIC CAPITAL LETTER IE WITH BREVE 是一個(gè)字符槐秧。這是相當(dāng)于 U+0415 CYRILLIC CAPITAL LETTER IE 和 U+0306 加上 BREVE.啄踊。這使得字符串比較有時(shí)變得困難了忧设。 GO 規(guī)范確目前沒有對(duì)這個(gè)問題過深研究。
ISO 8859 編碼和 Go 語(yǔ)言
ISO 8859 系列字符集都是 8 位字符集颠通,他們?yōu)闅W洲不同地區(qū)和其他一些地方設(shè)計(jì)址晕。他們有相同的 ASCII 并且都在地位,但高位不同蒜哀。據(jù)谷歌估計(jì)斩箫,ISO 8859 編碼了盡 20%的網(wǎng)頁(yè)吏砂。
第一個(gè)編碼字符集撵儿,ISO 8859-1 或叫做 Latin-1,前 256 個(gè)字符和 Unicode 相同狐血。 Latin-1 字符的 utf-16 和 ISO 8859-1 有相同的編碼淀歇。但是,這并不真的有用匈织,因?yàn)?UTF-16 是一個(gè) 16位的編碼字符集而 ISO 8859-1 是 8 位編碼浪默。 UTF-8 是一種 8 位編碼,但是高位用來表示更多的字符缀匕,所以只有 ASCII 的一部分是 utf-8 和 ISO 8859-1 相同纳决,所以UTF-8 并沒有多大實(shí)際用途(都是 8 位的)。
但 ISO8859 系列沒有任何復(fù)雜的問題乡小。每一組中的每個(gè)字符對(duì)應(yīng)一個(gè)唯一的 Unicode 字符阔加。例如,在 ISO 8859-2 中的字符“l(fā)atin capital letter I with ogonek”在 ISO 8859-2 是 0xc7(十六進(jìn)制)满钟,對(duì)應(yīng)的 Unicode 的 U+012E胜榔。 ISO 8859 字符集和 Unicode 字符集之間轉(zhuǎn)換其實(shí)只是一個(gè)表查找。
這個(gè)從 ISO 8859 到 Unicode 的查找表湃番,可以用一個(gè) 256 的數(shù)組完成夭织。因?yàn)椋S多字符索引相同吠撮。因此尊惰,我們只需要一個(gè)標(biāo)注不同索引的映射就可以。
ISO 8859-2 的映射為
var unicodeToISOMap = map[int] uint8 {
0x12e: 0xc7,
0x10c: 0xc8,
0x118: 0xca,
// plus more
}
從 utf-8 轉(zhuǎn)換成 ISO 8859-2 的函數(shù)
/*Turn a UTF-8 string into an ISO 8859 encoded byte array 8 string into an ISO 8859
*/
func unicodeStrToISO(str string) []byte {
// get the unicode code points
codePoints := []int(str)
// create a byte array of the same length
bytes := make([]byte, len(codePoints))
for n, v := range(codePoints) {
// see if the point is in the exception map tion map
iso, ok := unicodeToISOMap[v]
if !ok {
// just use the value
iso = uint8(v)
}
bytes[n] = iso
}
return bytes
}
同樣你可以將 ISO 8859-2 轉(zhuǎn)換為 utf-8
var isoToUnicodeMap = map[uint8] int {
0xc7: 0x12e,
0xc8: 0x10c,
0xca: 0x118,
// and more
}
func isoBytesToUnicode(bytes []byte) string {
codePoints := make([]int, len(bytes))
for n, v := range(bytes) {
unicode, ok :=isoToUnicodeMap[v]
if !ok {
unicode = int(v) unicode = int(v) unicode = int(v)
}
codePoints[n] = unicode
}
return string(codePoints)
}
這些函數(shù)可以用來將 ISO 8859-2 當(dāng)作 UTF-8 來讀寫泥兰。通過改變映射表弄屡,可以覆蓋其他的 ISO8859 字符集合。Latin-1 字符集(ISO 8859-1)是一個(gè)特殊的情況:地圖映射為空逾条,因?yàn)樽址?Latin-1 和 Unicode 中編碼相同琢岩。同樣的方法,你也可以使用其他字符集構(gòu)建映射表师脂,如Windows1252担孔。
其他字符集和 Go 語(yǔ)言
還有非常非常多的字符集編碼江锨。據(jù)谷歌稱,這些字符集通常只有很少地方使用糕篇,所以可能用的會(huì)更少啄育。但是,如果你的軟件要占據(jù)所有市場(chǎng)拌消,那么你可能需要對(duì)這些字符集進(jìn)行處理挑豌。
在最簡(jiǎn)單的情況下,查找表就夠了墩崩。但是氓英,這樣也不是總是奏效。ISO 2022 字符編碼方案通過……鹦筹。這是從日本某寫編碼中借用來個(gè)铝阐,相當(dāng)復(fù)雜。
Go 語(yǔ)言目前在語(yǔ)言本身和包文件上支持其他字符集铐拐。所以徘键,你要么避免使用其他字符集,雖然沒法和用這些字符集的程序共存遍蟋,要么自己動(dòng)手寫很多代碼吹害。
總結(jié)
這一章沒有什么代碼,卻有幾個(gè)非常復(fù)雜的概念虚青。當(dāng)然它呀,也取決于你:你要只滿足說美式英語(yǔ)的人,那問題就簡(jiǎn)單了挟憔;要是你的應(yīng)用也要讓其他人可用钟些,那你就要在這個(gè)復(fù)雜的問題上花點(diǎn)精力了。