如何理解字符编码
一直有個困惑,為什么計算機系統搞那么多字符編碼,就一個Unicode統一天下不就得了,后來看了篇文章,才多少理解一丁點。
英語的國家,只要一個字節就可以表示全部的字符,一個無符合的字節可以表示256個字符,對于英語的國家而言綽綽有余了。但是其它國家的字符就費勁了,如果使用Unicode編碼,囊括全部的字符,要統一天下,那么一個字節就遠遠不夠了,需要很多字節來表示一個字符(比如4個字節或者更多字節)才行,因為全球的字符實在TMD太多了。
好了咱們就這么定吧,Unicode編碼來表示全部的字符好嗎?居然有人反對了,說英文字母只用一個字節表示就夠了,如果Unicode統一規定,每個符號用三個或四個字節表示,那么每個英文字母前都必然有二到三個字節是0,這對于存儲來說是極大的浪費,文本文件的大小會因此大出二三倍,這是無法接受的。總之就是沒法達成一致,既然如此,那么就各家自己設計字符編碼方案吧,于是乎才有那么一堆UTF-8,GB2312,ISO 8859-1,GBK等等。
但是計算機內存只能保存Unicode編碼,這點則是統一的,而且必須統一,否則問題就大了。
如果沒有Unicode,會怎么樣,舉栗子說明下:
中國使用自己的字符集編碼GBK對字符串china編碼,假設編碼后的二進制碼就是:0111 0101,那么計算機就按0111 0101存儲china。那么美國用戶想在他們自己的計算機打開文件查看,就必須使用GBK解碼(他們的計算機必須安裝GBK編碼)才能看到china,這是肯定的。那么這種編碼和解碼的關系如下圖所示:
計算機是根據GBK編碼把中文字符串轉換成二進制碼后進行存儲的,假設GBK編碼表如下圖所示:
如上圖可以明確知道,字符編碼就是規定每個字符對應的二進制碼是什么。
文檔的內容其實是英文字母,對于美國用戶而言他們習慣使用UTF-8字符編碼,他們的計算機默認是使用UTF-8編碼和解碼的,他不希望每次打開這份文檔都要指定GBK解碼才能正常看內容,那么就必須把這份文檔改成UTF-8編碼保存才行,這時候問題來了,沒有中間碼(Unicode),那么怎么把文檔的編碼改成UTF-8呢?因為字符串china在UTF-8的規則里面,對應的二進制碼是1010 0010,如下圖所示:
就是說,計算機是根據GBK的規則把0111 0101轉換成字符串china顯示給用戶看的,也就是說計算機對文檔的數據理解就是根據GBK編碼規則來理解的。現在要按UTF-8保存文檔,就是把china轉換成10100010存儲,那么01110101如何變成10100010呢?沒法變,對吧!所以沒有中間碼(Unicode)就無法把GBK的文檔變成UTF-8的文檔。
再舉個栗子說明下:
計算機和編程語言都是西方國家發明的,這些計算機對編程語言的理解是基于二進制碼,因此計算機對這些二進制碼的含義認知應該已經固定和達成統一了,計算機的理解過程用下圖簡單表示:
上面的代碼根據計算機本來設計的編碼規則理解,可以得到如下圖所示的字節碼:
就是說JVM遇到0110,知道是int數據類型,遇到0010知道是=,遇到0111知道是+,所以根據這樣的理解,最終計算機知道代碼的含義,從而計算得到結果2并且打印輸出。
如果計算機沒有這樣默認的編碼規則,那怎么玩?GBK,UTF-8等等這些編碼方案都是后面才設計出來的,而且相同的二進制碼對應不同的編碼方案其含義還完全不同,所以你讓系統的指令集如何去理解這些二進制代碼。舉個比較形象的栗子:好比計算機是個“神人”,中國用戶用自己的語言把需求描述清楚了,日本用戶用自己的語言也把需求描述清楚了,這兩份需求要提交給“神人”去完成,“神人”怎么理解這兩份需求呢?你是不是要翻譯成“神人”理解的語言才行呀!“神人”只理解Unicode的,所以中國和日本用戶必須把需求翻譯成Unicode后,“神人”就理解兩位用戶的需求是啥了,于是“神人”就用自己的能力去完成需求的任務,得到兩位用戶想要的結果。你如果不要這個“神人”幫忙,想自己完成特定的需求任務可以嗎?當然可以,那你自己去創造個“神人”,來理解你的需求就行了。也就是說你自己創造個“機器”,它可以理解你用自己特定的編碼規則提交的代碼從而完成相應的任務,而且這個“機器”只能理解你的代碼,日本用戶的代碼理解不了,德國的代碼也理解不了。這樣你覺得滿意嗎?
所以說計算機必須有個初始的、固定的編碼規則,而GBK、UTF-8等編碼規則都只是把自己國家的字符所對應的Unicode轉換成自己的字符編碼而已。如下圖所示:
我再舉個例子,大家如果理解了就基本ok了,假設瀏覽器指定字符編碼是UTF-8,那我們看看String str = "小妹"的編碼是如何變化的:
代碼如下:
String c = "小妹";byte[] bs = c.getBytes("UTF-8"); // ① 使用UTF-8對"小妹"進行編碼,得到UTF-8編碼String c1 = new String(bs, "ISO-8859-1"); //② 使用ISO-8859-1對UTF-8的編碼進行解碼,得到錯誤的Unicode編碼byte[] bs1 = c1.getBytes("UNICODE"); // 獲取錯誤的Unicode編碼,其實就是字節數組byte[] bs2 = c1.getBytes("ISO-8859-1"); //③ 把錯誤的Unicode編碼,重新轉換成原來的UTF-8編碼,String c2 = new String(bs2,"UTF-8"); // ④使用UTF-8重新解碼得到正確的Unicode編碼System.out.println(c2); // ⑤小妹解讀如下:
字符串“小妹”在內存中的Unicode編碼是:11111110 11111111 01011100 00001111 01011001 10111001
接著瀏覽器使用UTF-8對“小妹”的Unicode進行編碼得到UTF-8編碼:11100101 10110000 10001111 11100101 10100110 10111001,接著就傳送給了服務器
服務器默認使用ISO-8859-1解碼,得到了錯誤的Unicode編碼:11111110 11111111 00000000 11100101 00000000 10110000 00000000 10001111 00000000 11100101 00000000 10100110 00000000 10111001
如果想要獲得正確的Unicode編碼,必須把錯誤的Unicode編碼重新編碼成原來的UTF-8編碼,因為原來解碼是用ISO-8859-1,所以錯誤的Unicode編碼要轉換成原來的UTF-8編碼,就必須使用ISO-8859-1進行編碼才行,得到了UTF-8編碼之后,再使用UTF-8重新解碼成正確的Unicode編碼就可以了。
上述編碼的變化過程可以看下圖:
P.S 解碼行為就是編碼行為,為了區別不同的編碼方向和編碼含義,所以規定字符從初始編碼轉換成其它編碼叫編碼,而從其它編碼轉換成初始編碼叫解碼
任何字符在計算機內存中都默認以Unicode編碼存在,所以“祖國”這個字符串初始是以Unicode編碼存在于內存中的,那么java程序用UTF-8把“祖國”從Unicode(就是字節數組)轉換成UTF-8編碼(就是字節數組)的過程叫“編碼”,java程序再用UTF-8(就是字節數組)將“祖國”從UTF-8編碼轉換回Unicode編碼(就是字節數組)的過程叫“解碼”。過程如下圖所示:
通常字符是以某種編碼保存在硬盤(磁盤)中,例如GBK,UTF-8,ISO-8859-1等,但是java程序在讀取字符到內存時,就必須以Unicode編碼存放,換句話說java程序在內存中處理字符的時候,會用到Unicode編碼,持久化存儲的時候都是以其它編碼方案保存。
用圖表述如下:
磁盤文件(假如以UTF-8編碼保存)加載至內存,JVM會以UTF-8解碼轉成Unicode編碼。
out.println()寫入response,容器會將response中的數據發送給瀏覽器,這樣數據會離開服務器內存一段時間再到用戶電腦的內存中,Unicode只在內存中出現,所以寫入response也要按某種字符編碼(假如也是UTF-8)對文檔的數據進行編碼后保存,瀏覽器拿到服務器發送過來的數據后,會以UTF-8解碼成Unicode編碼,再正常顯示出來。
總結
- 上一篇: IntelliJ IDEA for Ma
- 下一篇: IntelliJ IDEA for Ma