??計算機是以二進制的形式來存儲數(shù)據(jù)的,它只認識 0 和 1 兩個數(shù)字曹步,我們在屏幕上看到的文字宪彩,在存儲之前都被轉(zhuǎn)換成了二進制(0和1序列),在顯示時也要根據(jù)二進制找到對應(yīng)的字符讲婚。
??可想而知尿孔,特定的文字必然對應(yīng)著固定的二進制,否則在轉(zhuǎn)換時將發(fā)生混亂筹麸。那么活合,怎樣將文字與二進制對應(yīng)起來呢?這就需要有一套規(guī)范物赶,計算機公司和軟件開發(fā)者都必須遵守白指,這樣的一套規(guī)范就稱為字符集(Character Set)或者字符編碼(Character Encoding)。
??嚴格來說酵紫,字符集和字符編碼不是一個概念告嘲,字符集定義了文字和二進制的對應(yīng)關(guān)系,為字符分配了唯一的編號奖地,而字符編碼規(guī)定了如何將文字的編號存儲到計算機中橄唬。我們暫時先不討論這些細節(jié),姑且認為它們是一個概念参歹,本節(jié)中我也混用了這兩個概念轧坎,未做區(qū)分。
??字符集為每個字符分配一個唯一的編號泽示,類似于學生的學號缸血,通過編號就能夠找到對應(yīng)的字符。
??可以將字符集理解成一個很大的表格械筛,它列出了所有字符和二進制的對應(yīng)關(guān)系捎泻,計算機顯示文字或者存儲文字,就是一個查表的過程埋哟。
??在計算機逐步發(fā)展的過程中笆豁,先后出現(xiàn)了幾十種甚至上百種字符集,有些還在使用赤赊,有些已經(jīng)淹沒在了歷史的長河中闯狱,本節(jié)我們先講解的是一種專門針對英文的字符集——ASCII編碼。
拉丁字母(開胃小菜)
??在正式介紹 ASCII 編碼之前抛计,我們先來說說什么是拉丁字母哄孤。估計也有不少讀者和我一樣,對于拉丁字母吹截、英文字母和漢語拼音中的字母的關(guān)系不是很清楚瘦陈。
??拉丁字母也叫羅馬字母凝危,它源自希臘字母,是當今世界上使用最廣的字母系統(tǒng)晨逝《昴基本的拉丁字母就是我們經(jīng)常見到的 ABCD 等26個英文字母。
拉丁字母捉貌、阿拉伯字母支鸡、斯拉夫字母(西里爾字母)被稱為世界三大字母體系。
拉丁字母原先是歐洲人使用的趁窃,后來由于歐洲殖民主義牧挣,導(dǎo)致這套字母體系在全球范圍內(nèi)開始流行,美洲棚菊、非洲浸踩、澳洲、亞洲都沒有逃過西方文化的影響统求。中國也是检碗,我們現(xiàn)在使用的拼音其實就是拉丁字母,是不折不扣的舶來品码邻。
??后來折剃,很多國家對 26 個基本的拉丁字母進行了擴展,以適應(yīng)本地的語言文化像屋。最常見的擴展方式就是加上變音符號怕犁,例如漢語拼音中的ü,就是在u的基礎(chǔ)上加上兩個小點演化而來己莺;再如奏甫,áà就是在a的上面標上音調(diào)。
總起來說:
基本拉丁字母就是 26 個英文字母凌受;
擴展拉丁字母就是在基本的 26 個英文字母的基礎(chǔ)上添加變音符號阵子、橫線、斜線等演化而來胜蛉,每個國家都不一樣挠进。
ASCII 編碼
??計算機是美國人發(fā)明的,他們首先要考慮的問題是誊册,如何將二進制和英文字母(也就是拉丁文)對應(yīng)起來领突。
??當時,各個廠家或者公司都有自己的做法案怯,編碼規(guī)則并不統(tǒng)一君旦,這給不同計算機之間的數(shù)據(jù)交換帶來不小的麻煩。但是相對來說,能夠得到普遍認可的有 IBM 發(fā)明的 EBCDIC 和此處要談的 ASCII于宙。
??我們先說 ASCII浮驳。ASCII 是“American Standard Code for Information Interchange”的縮寫悍汛,翻譯過來是“美國信息交換標準代碼”捞魁。看這個名字就知道离咐,這套編碼是美國人給自己設(shè)計的谱俭,他們并沒有考慮歐洲那些擴展的拉丁字母,也沒有考慮韓語和日語宵蛀,我大中華幾萬個漢字更是不可能被重視昆著。
但這也無可厚非,美國人自己發(fā)明的計算機术陶,當然要先解決自己的問題
??ASCII 的標準版本于 1967 年第一次發(fā)布凑懂,最后一次更新則是在 1986 年,迄今為止共收錄了 128 個字符梧宫,包含了基本的拉丁字母(英文字母)接谨、阿拉伯數(shù)字(也就是 1234567890)、標點符號(,.!等)塘匣、特殊符號(@#$%^&等)以及一些具有控制功能的字符(往往不會顯示出來)脓豪。
??在 ASCII 編碼中,大寫字母忌卤、小寫字母和阿拉伯數(shù)字都是連續(xù)分布的(見下表)扫夜,這給程序設(shè)計帶來了很大的方便。例如要判斷一個字符是否是大寫字母驰徊,就可以判斷該字符的 ASCII 編碼值是否在 65~90 的范圍內(nèi)笤闯。
??EBCDIC 編碼正好相反,它的英文字母不是連續(xù)排列的棍厂,中間出現(xiàn)了多次斷續(xù)颗味,給編程帶來了一些困難。現(xiàn)在連 IBM 自己也不使用 EBCDIC 了勋桶,轉(zhuǎn)而使用更加優(yōu)秀的 ASCII脱衙。
??ASCII 編碼已經(jīng)成了計算機的通用標準,沒有人再使用 EBCDIC 編碼了例驹,它已經(jīng)消失在歷史的長河中了捐韩。
??ASCII、GB2312鹃锈、GBK荤胁、Shift_Jis、ISO/IEC 8859 等地區(qū)編碼都是各個國家為了自己的語言文化開發(fā)的屎债,不具有通用性仅政,在一種編碼下開發(fā)的軟件或者編寫的文檔垢油,拿到另一種編碼下就會失效,必須提前使用程序轉(zhuǎn)碼圆丹,非常麻煩滩愁。
??人們迫切希望有一種編碼能夠統(tǒng)一世界各地的字符,計算機只要安裝了這一種字編碼辫封,就能支持使用世界上所有的文字硝枉,再也不會出現(xiàn)亂碼,再也不需要轉(zhuǎn)碼了倦微,這對計算機的數(shù)據(jù)傳遞來說是多么的方便呀妻味!
??就在這種呼吁下,Unicode誕生了欣福。Unicode 也稱為統(tǒng)一碼责球、萬國碼;看名字就知道拓劝,Unicode 希望統(tǒng)一所有國家的字符編碼雏逾。
??Unicode 于 1994 年正式公布第一個版本,現(xiàn)在的規(guī)脑浣可以容納 100 多萬個符號校套,是一個很大的集合。
有興趣的讀取可以轉(zhuǎn)到 https://unicode-table.com/cn/ 查看 Unicode 包含的所有字符牧抵,以及各個國家的字符是如何分布的笛匙。
這個網(wǎng)站不太穩(wěn)定,隨時可能無法訪問犀变,不要問我為什么妹孙,訪問不了也不要找我,沒有比它更好的網(wǎng)站了获枝。
??Windows蠢正、Linux、Mac OS 等常見操作系統(tǒng)都已經(jīng)從底層(內(nèi)核層面)開始支持 Unicode省店,大部分的網(wǎng)頁和軟件也使用 Unicode嚣崭,Unicode 是大勢所趨。
??不過由于歷史原因懦傍,目前的計算機仍然安裝了 ASCII 編碼以及 GB2312雹舀、GBK、Big5粗俱、Shift-JIS 等地區(qū)編碼说榆,以支持不使用 Unicode 的軟件或者文檔。內(nèi)核在處理字符時,一般會將地區(qū)編碼先轉(zhuǎn)換為 Unicode签财,再進行下一步處理串慰。
Unicode 字符集是如何存儲的
??本節(jié)我們多次說 Unicode 是一套字符集,而不是一套字符編碼唱蒸,它們之間究竟有什么區(qū)別呢邦鲫?
??嚴格來說,字符集和字符編碼不是一個概念:
??字符集定義了字符和二進制的對應(yīng)關(guān)系油宜,為每個字符分配了唯一的編號掂碱×耍可以將字符集理解成一個很大的表格慎冤,它列出了所有字符和二進制的對應(yīng)關(guān)系,計算機顯示文字或者存儲文字沧卢,就是一個查表的過程蚁堤。
??而字符編碼規(guī)定了如何將字符的編號存儲到計算機中。如果使用了類似 GB2312 和 GBK 的變長存儲方案(不同的字符占用的字節(jié)數(shù)不一樣)但狭,那么為了區(qū)分一個字符到底使用了幾個字節(jié)披诗,就不能將字符的編號直接存儲到計算機中,字符編號在存儲之前必須要經(jīng)過轉(zhuǎn)換立磁,在讀取時還要再逆向轉(zhuǎn)換一次呈队,這套轉(zhuǎn)換方案就叫做字符編碼。
??有的字符集在制定時就考慮到了編碼的問題唱歧,是和編碼結(jié)合在一起的宪摧,例如 ASCII、GB2312颅崩、GBK几于、BIG5 等,所以無論稱作字符集還是字符編碼都無所謂沿后,也不好區(qū)分兩者的概念沿彭。而有的字符集只管制定字符的編號,至于怎么存儲尖滚,那是字符編碼的事情喉刘,Unicode 就是一個典型的例子,它只是定義了全球文字的唯一編號漆弄,我們還需要 UTF-8睦裳、UTF-16、UTF-32 這幾種編碼方案將 Unicode 存儲到計算機中置逻。
Unicode 可以使用的編碼方案有三種推沸,分別是:
UFT-8:一種變長的編碼方案,使用 1~6 個字節(jié)來存儲;
UFT-32:一種固定長度的編碼方案鬓催,不管字符編號大小肺素,始終使用 4 個字節(jié)來存儲;
UTF-16:介于 UTF-8 和 UTF-32 之間宇驾,使用 2 個或者 4 個字節(jié)來存儲倍靡,長度既固定又可變。
UTF 是 Unicode Transformation Format 的縮寫课舍,意思是“Unicode轉(zhuǎn)換格式”塌西,后面的數(shù)字表明至少使用多少個比特位(Bit)來存儲字符。
- UTF-8
UTF-8 的編碼規(guī)則很簡單:
如果只有一個字節(jié)筝尾,那么最高的比特位為 0捡需,這樣可以兼容 ASCII;
如果有多個字節(jié)筹淫,那么第一個字節(jié)從最高位開始站辉,連續(xù)有幾個比特位的值為 1,就使用幾個字節(jié)編碼损姜,剩下的字節(jié)均以 10 開頭饰剥。
具體的表現(xiàn)形式為:
0xxxxxxx:單字節(jié)編碼形式,這和 ASCII 編碼完全一樣摧阅,因此 UTF-8 是兼容 ASCII 的汰蓉;
110xxxxx 10xxxxxx:雙字節(jié)編碼形式(第一個字節(jié)有兩個連續(xù)的 1);
1110xxxx 10xxxxxx 10xxxxxx:三字節(jié)編碼形式(第一個字節(jié)有三個連續(xù)的 1)棒卷;
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx:四字節(jié)編碼形式(第一個字節(jié)有四個連續(xù)的 1)顾孽。
xxx 就用來存儲 Unicode 中的字符編號。
- UTF-32
UTF-32 是固定長度的編碼娇跟,始終占用 4 個字節(jié)岩齿,足以容納所有的 Unicode 字符,所以直接存儲 Unicode 編號即可苞俘,不需要任何編碼轉(zhuǎn)換盹沈。浪費了空間,提高了效率吃谣。 - UTF-16
UFT-16 比較奇葩乞封,它使用 2 個或者 4 個字節(jié)來存儲。
對于 Unicode 編號范圍在 0 ~ FFFF 之間的字符岗憋,UTF-16 使用兩個字節(jié)存儲肃晚,并且直接存儲 Unicode 編號,不用進行編碼轉(zhuǎn)換仔戈,這跟 UTF-32 非常類似关串。
對于 Unicode 編號范圍在 10000~10FFFF 之間的字符拧廊,UTF-16 使用四個字節(jié)存儲,具體來說就是:將字符編號的所有比特位分成兩部分晋修,較高的一些比特位用一個值介于 D800~DBFF 之間的雙字節(jié)存儲吧碾,較低的一些比特位(剩下的比特位)用一個值介于 DC00~DFFF 之間的雙字節(jié)存儲。
對比以上三種編碼方案
首先墓卦,只有 UTF-8 兼容 ASCII倦春,UTF-32 和 UTF-16 都不兼容 ASCII,因為它們沒有單字節(jié)編碼落剪。
- UTF-8 使用盡量少的字節(jié)來存儲一個字符睁本,不但能夠節(jié)省存儲空間,而且在網(wǎng)絡(luò)傳輸時也能節(jié)省流量忠怖,所以很多純文本類型的文件(例如各種編程語言的源文件呢堰、各種日志文件和配置文件等)以及絕大多數(shù)的網(wǎng)頁(例如百度、新浪脑又、163等)都采用 UTF-8 編碼暮胧。
UTF-8 的缺點是效率低,不但在存儲和讀取時都要經(jīng)過轉(zhuǎn)換问麸,而且在處理字符串時也非常麻煩。例如钞翔,要在一個 UTF-8 編碼的字符串中找到第 10 個字符严卖,就得從頭開始一個一個地檢索字符,這是一個很耗時的過程布轿,因為 UTF-8 編碼的字符串中每個字符占用的字節(jié)數(shù)不一樣哮笆,如果不從頭遍歷每個字符,就不知道第 10 個字符位于第幾個字節(jié)處汰扭,就無法定位稠肘。
不過,隨著算法的逐年精進萝毛,UTF-8 字符串的定位效率也越來越高了项阴,往往不再是槽點了。
- UTF-32 是“以空間換效率”笆包,正好彌補了 UTF-8 的缺點环揽,UTF-32 的優(yōu)勢就是效率高:UTF-32 在存儲和讀取字符時不需要任何轉(zhuǎn)換,在處理字符串時也能最快速地定位字符庵佣。例如歉胶,在一個 UTF-32 編碼的字符串中查找第 10 個字符,很容易計算出它位于第 37 個字節(jié)處巴粪,直接獲取就行通今,不用再逐個遍歷字符了粥谬,沒有比這更快的定位字符的方法了。
但是辫塌,UTF-32 的缺點也很明顯帝嗡,就是太占用存儲空間了,在網(wǎng)絡(luò)傳輸時也會消耗很多流量璃氢。我們平常使用的字符編碼值一般都比較小哟玷,用一兩個字節(jié)存儲足以,用四個字節(jié)簡直是暴殄天物一也,甚至說是不能容忍的巢寡,所以 UTF-32 在應(yīng)用上不如 UTF-8 和 UTF-16 廣泛。
- UTF-16 可以看做是 UTF-8 和 UTF-32 的折中方案椰苟,它平衡了存儲空間和處理效率的矛盾抑月。對于常用的字符,用兩個字節(jié)存儲足以舆蝴,這個時候 UTF-16 是不需要轉(zhuǎn)換的谦絮,直接存儲字符的編碼值即可。
Windows 內(nèi)核洁仗、.NET Framework层皱、Cocoa、Java String 內(nèi)部采用的都是 UTF-16 編碼赠潦。UTF-16 是幕后的功臣叫胖,我們在編輯源代碼和文檔時都是站在前臺,所以一般感受不到她奥,其實很多文本在后臺處理時都已經(jīng)轉(zhuǎn)換成了 UTF-16 編碼瓮增。
不過,UNIX 家族的操作系統(tǒng)(Linux哩俭、Mac OS绷跑、iOS 等)內(nèi)核都采用 UTF-8 編碼,我們就不去爭論誰好誰壞了凡资。
寬字符和窄字符(多字節(jié)字符)
有的編碼方式采用 1~n 個字節(jié)存儲砸捏,是變長的,例如 UTF-8讳苦、GB2312带膜、GBK 等;如果一個字符使用了這種編碼方式鸳谜,我們就將它稱為多字節(jié)字符膝藕,或者窄字符。
有的編碼方式是固定長度的咐扭,不管字符編號大小芭挽,始終采用 n 個字節(jié)存儲滑废,例如 UTF-32、UTF-16 等袜爪;如果一個字符使用了這種編碼方式蠕趁,我們就將它稱為寬字符。
Unicode 字符集可以使用窄字符的方式存儲辛馆,也可以使用寬字符的方式存儲俺陋;GB2312、GBK昙篙、Shift-JIS 等國家編碼一般都使用窄字符的方式存儲腊状;ASCII 只有一個字節(jié),無所謂窄字符和寬字符苔可。
Go語言中的Unicode與utf-8
Go一個string類型的值在底層是怎樣被表達的缴挖?
一般回答:在底層,一個string類型的值是由一系列相對應(yīng)的Unicode代碼點的UTF-8編碼值來表達
問題解析
??在Go語言中焚辅,一個string類型的值既可以被拆分為一個包含多個字符的序列映屋,也可以被拆分成一個包含多個字節(jié)的序列。前者可以由一個rune為元素的切片來表示同蜻,而后者可以由一byte為元素類型的切片代表棚点。
rune是Go語言中特有的一個基本數(shù)據(jù)類型,它的一個值就代表一個字符埃仪,即:一個Unicode字符乙濒。比如,'G', 'o', '愛'卵蛉,'好','者'代表的就是一個Unicode字符么库。
我們已經(jīng)知道傻丝,UTF-8UTF-8編碼方案會把一個Unicode字符編碼為要一個長度在[1, 4]范圍內(nèi)的字節(jié)序列。所以诉儒,一個rune類型的值也可以由一個或多個字節(jié)來表示葡缰。
type rune = int32
根據(jù)rune類型的聲明可知,它實際上就是int32類型的一個別名類型忱反。也就是說泛释,一個rune類型的值會由四個字節(jié)寬度的空間來存儲。它的存儲空間總是能存下一個UTF-8編碼值温算。
一個rune類型的值在底層其實就是一個UTF-8編碼值怜校。前者是便于人類理解的外部展現(xiàn),后者是便于計算機理解的內(nèi)在表達注竿。