為什么采用中文編程不行?
一、背景介紹
很多剛接觸計算機的同學(xué),可能會發(fā)出一個疑問,為什么不能直接使用中文編程?
要了解這個問題,還得從計算機的起源說起!
在計算機軟件里面,一切的信息都可以用 1 和 0 來表示(嚴(yán)格說連 0 和 1 都沒有,只有開和關(guān)),也被稱為二進(jìn)制位,英文簡稱:bit,音譯為“比特”,比特是計算機內(nèi)存中的最小單位(也稱原子單位),在計算機系統(tǒng)中,每 bit 可用 0 或 1 表示數(shù)位訊號。
在上篇文章中,我們了解到不管是磁盤還是網(wǎng)絡(luò)傳輸,最小的存儲單元都是字節(jié)。
有的同學(xué)可能又會發(fā)出疑問,為什么不直接使用比特存儲?字節(jié)和比特又有什么關(guān)系呢?
雖然比特是硬件上的最小單元,但是光靠 1 和 0 很難知道是什么意思,比特就好比身體的細(xì)胞,由于顆粒度太細(xì),很難知道這個細(xì)胞屬于那個地方,于是就有了字節(jié)這個概念,字節(jié)就好比身體的某個器官,更便于識別。
簡單的說,從單位換算角度,一個字節(jié) = 8 個比特!
通過這一串的 8 個 1 和 0 的不同排列方式,可以表達(dá)出 256 個(2的8次方)不同的意思,這樣換算率在當(dāng)時的美國科學(xué)家看來,已經(jīng)足夠表達(dá)英文中全部字母大小寫及符號加控制符了,也就是下文我們要介紹的 ASCII 字母代碼表。
上個世紀(jì) 60 年代,為了更好的便于計算機傳輸字符信息,美國制定了一套字符編碼規(guī)則,對英語字符與二進(jìn)制位之間的關(guān)系做了統(tǒng)一規(guī)定,這編碼規(guī)則被稱為 ASCII 編碼(美國標(biāo)準(zhǔn)信息交換碼),一直沿用至今。
ASCII 編碼一共規(guī)定了 128 個字符的編碼規(guī)則,這 128 個字符形成的集合就叫做ASCII 字符集。
在早期的 ASCII 編碼中,規(guī)定使用單字節(jié)中低位的 7 個比特去編碼所有的字符,每個字符占用一個字節(jié)的后面7位,最前面的1位統(tǒng)一規(guī)定為 0。
在這個編碼規(guī)則下,當(dāng)你在鍵盤上輸入字母 A,計算機會根據(jù) ASCII 字符代碼表,找到對應(yīng)的十進(jìn)制碼值 65,然后換算成二進(jìn)制碼值 01000001,傳輸?shù)侥康牡?;接受端收到信號之后,會將二進(jìn)制碼值 01000001 再換算成十進(jìn)制碼值 65,然后再根據(jù)字符代碼表,將十進(jìn)制碼值 65 解碼成字母 A,最后輸出到控制臺。
由此,整個計算機之間的信息傳輸交換完成!
在 ASCII 編碼中,編號 0~31 是控制字符如換行回車刪除等,32~126 是可打印字符,可以通過鍵盤輸入并且能夠顯示出來,一個英文字符占用一個字節(jié)。
對于英語來說,用 128 個符號編碼就夠了,但是隨著計算機的快速發(fā)展,用來表示其他語言,128 個符號是遠(yuǎn)遠(yuǎn)不夠的。
所以當(dāng) ASCII 碼到歐洲的時候,一些歐洲國家就決定對 ASCII 編碼進(jìn)行適當(dāng)?shù)?擴展和改造,現(xiàn)有的編碼規(guī)則維持不變,把字節(jié)中閑置的最高位也編入新的符號。比如,法語中的 é 的編碼為 130(二進(jìn)制 10000010 )。這樣一來,這些歐洲國家使用的編碼體系,可以表示最多 256 個符號,這個編碼統(tǒng)稱為 EASCII(Extended ASCII)。
但是歐洲的語言體系有個特點:小國家特別多,每個國家可能都有自己的語言體系,語言環(huán)境十分復(fù)雜。因此即使 EASCII 可以表示 256 個字符,也不能統(tǒng)一歐洲的語言環(huán)境。
為了解決上面這個問題,歐洲的工程師們想出了一個折中的方案:在 EASCII 中表示的 256 個字符中,前 128 字符和 ASCII 編碼表示的字符完全一樣,后 128 個字符每個國家或地區(qū)都有自己的編碼標(biāo)準(zhǔn)。
比如,130 在法語編碼中代表了 é,但是在希伯來語編碼中代表字母 Gimel (?),在俄語編碼中又會代表另一個符號。但是不管怎樣,所有這些編碼方式中,0—127 表示的符號是一樣的,不一樣的只是 128—255 的這一段。
根據(jù)這個規(guī)則,就形成了很多子標(biāo)準(zhǔn):ISO-8859-1、ISO-8859-2、ISO-8859-3、……、ISO-8859-16。這些子標(biāo)準(zhǔn)適用于歐洲不同的國家地區(qū)。具體關(guān)于 ISO-8859 的標(biāo)準(zhǔn)請參考這個鏈接地址。
到了亞洲國家,使用的文字符號就更多了,漢字就多達(dá) 10 萬多個。根據(jù)上面的信息,我們知道一個字節(jié)最多只能表示 256 種符號,這對于漢字來說肯定是不夠的,必須使用多個字節(jié)表達(dá)一個符號。因此才出現(xiàn)了后面的 GB2312、Unicode 等字符集,簡體中文常見的編碼方式是 GB2312,使用兩個字節(jié)表示一個漢字,所以理論上最多可以表示 65536 個符號;而 Unicode 字符集是一個很大的字符集合,最多可以使用 4 個字節(jié)來表示一個符號,可以容納 100 多萬個符號。
關(guān)于字符集的故事發(fā)展,我們在此不過深入的講解,有興趣的朋友可以看看這個鏈接地址!
下面我們重點介紹一下 Unicode 字符集!
二、Unicode 字符集
在上文的信息中,我們了解到不同的國家有不同的字符集,如果通過電子郵件把信息傳送到另外一個國家的計算機系統(tǒng)中,看到的可能就不是那個原始發(fā)送的字符了,很有可能而是亂碼!
因為計算機里面并沒有真正的字符,字符都是以數(shù)字的形式存在的,通過郵件傳送一個字符,實際上傳送的是這個字符對應(yīng)的字符編碼,同一個數(shù)字在不同的國家和地區(qū)代表的很可能是不同的符號。
為了解決各個國家和地區(qū)之間各自使用不同的本地化字符編碼帶來的不便,工程師們將全世界所有的符號進(jìn)行了統(tǒng)一編碼,稱之為 Unicode,也被稱為統(tǒng)一碼、萬國碼。
所有字符不再區(qū)分國家和地區(qū),都是人類共有的符號,如"中"字在 Unicode 中不再是 GBK 中的 D6D0,而是在任何地方都是 4e2d,如果所有的計算機系統(tǒng)都使用這種編碼方式,那么 4e2d 這個字在任何地方都代表漢字中的"中"。
需要注意的是,Unicode 只是一個字符集,它只規(guī)定了符號的二進(jìn)制代碼,卻沒有規(guī)定這個二進(jìn)制代碼應(yīng)該如何編碼如何存儲。這就造成了兩個問題:
- 問題1:如何才能區(qū)別 Unicode 和 ASCII ?計算機怎么知道三個字節(jié)表示一個符號,而不是分別表示三個符號呢?
- 問題2:我們知道,英文字母只用一個字節(jié)表示就夠了,如果 unicode 統(tǒng)一規(guī)定,每個符號用三個或四個字節(jié)表示,那么每個英文字母前都必然有二到三個字節(jié)是 0,這對于存儲來說是極大的浪費,文本文件的大小會因此大出二三倍,這對當(dāng)時存儲器來說,是無法滿足的。
為了解決 Unicode 字符集中的一些問題,就出現(xiàn)了 UTF(Unicode Transformation Formats) 系列的編碼規(guī)則。UTF 編碼規(guī)則具體規(guī)定了 Unicode 字符集中的字符是如何編碼的。
下面我們就來看看 UTF 系列編碼的具體實現(xiàn)。
三、UTF 編碼規(guī)則
3.1、UTF-16
早期,Unicode 轉(zhuǎn)換格式規(guī)定不管什么字符都使用兩個字節(jié)表示,兩個字節(jié)其實就是 16 Bit,所以叫做 UTF-16。
UTF-16 編碼非常方便,每兩個字節(jié)表示一個字符,這個在字符串操作時大大簡化了操作,編碼效率也比較高,尤其適合在本地磁盤和內(nèi)存之間操作,可以進(jìn)行字符和字節(jié)之間的快速切換。
但是缺陷也很明顯,首先就是一個字符占用兩個字節(jié),因為很大一部分字符用一個字節(jié)表示就夠了,現(xiàn)在需要用兩個字節(jié),存儲空間放大了一倍;其次在網(wǎng)絡(luò)之間傳輸數(shù)據(jù),容易因為大小端問題,傳輸后讀取的數(shù)據(jù)會出現(xiàn)亂碼。
3.2、UTF-8
隨著互聯(lián)網(wǎng)的普及,強烈要求出現(xiàn)一種統(tǒng)一的編碼方式,為了解決 UTF-16 中的缺陷,基于此又誕生了一種可變長度技術(shù),每個編碼區(qū)域有不同的字節(jié)長度,不同類型的字符可以是由 1~4 個字節(jié)組成,這種編碼規(guī)則我們成它為 UTF-8,由 Ken Thompson 于1992年創(chuàng)建,用在網(wǎng)頁上可以統(tǒng)一展示頁面上的中文英文繁體及其它語言正常顯示。
UTF-8 最大的一個特點,就是它是一種變長的編碼方式。它使用 1~4 個字節(jié)表示一個符號,根據(jù)不同的符號而變化字節(jié)長度,UTF-8 編碼可以容納 2^21 個字符,總共 200 多萬個字符。
UTF-8的編碼規(guī)則很簡單,只有二條:
- 1.對于單字節(jié)的符號,字節(jié)的第一位設(shè)為0,后面7位為這個符號的 unicode碼。因此對于英語字母,UTF-8 編碼和 ASCII 碼是相同的,可以完全兼容過去的編碼規(guī)則
- 2.對于 n 字節(jié)的符號(n>1),第一個字節(jié)的前 n 位都設(shè)為1,第 n+1 位設(shè)為0,后面字節(jié)的前兩位一律設(shè)為 10。剩下的沒有提及的二進(jìn)制位,全部為這個符號的 unicode 碼
對不同范圍的字符使用不同長度的編碼方式,詳細(xì)的規(guī)則如下,其中字母 x 表示可用編碼的二進(jìn)制位。
比如『漢』這個字的 Unicode 編碼是 0x6C49。0x6C49 在 0x0800 ~ 0xFFFF 之間,使用 3 字節(jié)模板:1110xxxx 10xxxxxx 10xxxxxx。將 0x6C49 寫成二進(jìn)制是:0110 1100 0100 1001, 用這個比特流依次代替模板中的x,得到:11100110 10110001 10001001。
關(guān)于 UTF-8 編碼技術(shù)更加詳細(xì)的解說,可以參考這個鏈接!
四、Java 與字符編碼
Java 語言內(nèi)部使用的是 Unicode 字符集,采用 UTF-16 方式編碼字符。
但其實,Java 內(nèi)部還實現(xiàn)了ASCII、LATIN1、ISO8859-1、UTF-8、GBK 等字符集的編碼規(guī)則,可以很容易實現(xiàn)這些編碼之間的相互轉(zhuǎn)換。
在保證跨平臺特性的前提下,也支持了全擴展的本地平臺字符集,默認(rèn)顯示輸出和鍵盤輸入都是采用的本地編碼規(guī)則,因此,免不了二者的轉(zhuǎn)化問題。
以 windows 操作系統(tǒng)為例,我們看一個簡單的例子!
public static void main(String[] args) throws Exception {
// 我們采用 GBK 進(jìn)行編碼
byte b[] = "我們一起來學(xué)習(xí) Java 語言".getBytes("GBK");
File file = new File("encoding.txt");
OutputStream out = new FileOutputStream(file);
out.write(b);
out.close();
}
打開輸出的文件,內(nèi)容如下:
我們一起來學(xué)習(xí) Java 語言
正常情況下輸出,無編碼問題,但是如果改成這樣呢
public static void main(String[] args) throws Exception {
// 我們采用 ISO8859-1 進(jìn)行編碼
byte b[] = "我們一起來學(xué)習(xí) Java 語言".getBytes("ISO8859-1");
File file = new File("encoding.txt");
OutputStream out = new FileOutputStream(file);
out.write(b);
out.close();
}
輸出的文件,內(nèi)容如下:
?????Java??
亂碼問題就出現(xiàn)了!
原因相信大家都知道了,就是字符編碼和解碼的規(guī)則不一樣導(dǎo)致的。
Java 中的各個類,對于英文字符的支持都非常好,可以正常地寫入文件中,但對于中文字符就未必了!
從 Java 源代碼到寫入文件正確的內(nèi)容,要經(jīng)過 Java 源代碼 -> Java 字節(jié)碼 -> 虛擬機 -> 文件幾個步驟,在上述過程中的每一步都必須正確地處理漢字的編碼,才能夠使最終有我們期望的結(jié)果。
其中 Java 源代碼 -> Java 字節(jié)碼這一步驟,Java 編譯器 Javac 使用的字符集是系統(tǒng)默認(rèn)的字符集,比如在中文 Windows 操作系統(tǒng)上就是 GBK,而在 Linux 操作系統(tǒng)上是 ISO8859-1。所以經(jīng)常有同學(xué)發(fā)出疑問,自己在本地的 windows 系統(tǒng)上運行的很正常,但是把代碼部署到了 Linux 操作系統(tǒng)上編譯的類中源文件中的中文字符就出現(xiàn)亂碼了。
解決辦法就是在編譯的時候添加 encoding 參數(shù),并指定對應(yīng)的編碼規(guī)則,比如 GBK 或者 UTF-8,這樣才能夠與平臺無關(guān)。
如果想要查詢 jdk 使用的是哪種編碼規(guī)則,可以通過如下方式查詢:
public static void main(String[] args) {
System.getProperties().list(System.out);
}
輸出的內(nèi)容比較多,重點看下file.encoding
變量值就可以,比如小編當(dāng)前的電腦顯示結(jié)果如下:
file.encoding=GBK
表明了 JDK 使用的是 GBK 字符集,當(dāng)對字符串進(jìn)行操作時,都做了 Unicode 到 GBK 的轉(zhuǎn)換,既然 JDK 用的 GBK 編碼,那么用 ISO8859-1 字符集顯示 GBK 編碼出來的中文當(dāng)然是有問題的。
因此在實際使用過程中,推薦大家統(tǒng)一編碼規(guī)則,比如采用比較通用的 UTF-8 編碼規(guī)則,可以避免無端的文字亂碼問題。
五、小結(jié)
最近網(wǎng)上有傳聞?wù)f,采用中文來編程,大家可以試想一下,采用中文來編程會是個什么樣的結(jié)果?
通過上面的分析,我們可以得出一個結(jié)論,那就是采用中文編程,如果沒有統(tǒng)一編碼規(guī)則的情況下,會是個災(zāi)難;其次也會增加程序員們的工作難度,因為從字節(jié)來看,一個漢字至少等于英文的兩個字符,所以使用漢字會更加占內(nèi)存。
還有一點就是,英文最多也就 26 個字符,比較簡單,在所有的計算機上都非常通用,如果換成中文的話,截止目前,中文的符號已經(jīng)超過 10 萬個了,還沒有完全收集全,如果換成中文來編程,需要窮舉所有的中文字符,以防干擾程序的正常執(zhí)行,這在目前看來基本弊大于利!
六、參考
1、知乎 - 爐石不傳說 - 字符、字符集、字符編碼的基礎(chǔ)知識科普
2、博客園 - 五月的倉頡 - 字符編碼
3、知乎 - 韓蘭若 - UTF-8 到底是什么意思?unicode編碼簡介