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