Java IO4:字符編碼


前言

字符編碼,這本不屬於IO的內容,但字節流之后寫的應該是字符流,既然是字符流,那就涉及一個"字符編碼的"問題,考慮到字符編碼不僅僅是在IO這塊,Java中很多場景都涉及到這個概念,因此這邊文章就專門詳細寫一下字符編碼,具體的網上有很多,但本文目的是盡量講清楚各種編碼方式的作用,個人認為,不求、也沒有必要對字符編碼理解地多么深入。

 

字符集和字符編碼

第一個概念就是字符集和字符編碼之間的區別:

1、字符集(charset)

字符集指的是一個系統支持的所有抽象字符的集合。字符是各種文字和符號的總稱,包括各國家文字、標點符號、圖形符號、數字等,常見的字符集有ASCII字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等。

2、字符編碼(encoding)

計算機要准確處理各種字符集文字,就要進行字符編碼,以便計算機能夠識別和存儲各種文字。因此字符編碼就是講符號轉換為計算機可以接受的數字系統的數,稱為數字代碼。

 

ASCII碼

計算機里面只有數字0和1(嚴格說連0和1都沒有,只有開和關,無非是用0和1表示開關的狀態罷了),在計算機軟件里的一切都是用數字標識的額,屏幕上顯示的一個一個字符也是數字。最初使用的計算機在美國,用到的字符很少,因此每一個字符都用一個數字表示,一個字節所能表示的數字反內衛足以容納所有這些字符。實際上表示這些字符的數字的字節最高位都是0,也就是說這些數字都在0~127之間,如字符a對應97,字符b對應數字98,這種字符與數字的對應編碼固定下來之后,這套編碼規則被稱為ASCII碼(美國標准信息交換碼)。一張簡單的ASCII碼表如圖:

從表中可以看出ASCII碼分為兩部分:

1、0~31是控制字符,如換行、回車、刪除等

2、32~126是打印字符,可以通過鍵盤輸入並且能夠顯示出來

 

GB2312和GBK

隨着計算機在其它國家的普及,許多國家把本地字符集引入了計算機,大大擴展了計算機中字符的范圍。一個字節所能表示的范圍不足以容納中文字符(看看上面的ASCII碼表就知道了),中國大陸將每一個中文字符都用兩個字節表示,原有的ASCII碼字符的編碼保持不變。

為了將一個中文字符與兩個ASCII碼字符相區別,中文字符的每個字節最高位為1,中國大陸為每一個中文字符都指定了一個對應的數字,並於1980年制定了一套《信息技術 中文編碼字符集》,這套規范就是GB2312。GB2312是雙字節編碼,總的編碼范圍是A1~F7,其實A1~A9是富豪區,總共包含682個符號;B0~F7是漢字區,總共包含6763個漢字。

GBK是在1995年制定的后續標准,全稱為《漢字內碼擴展規范》,是國家技術監督局為Windows 95所制定的新的漢字內碼規范。GBK的出現是為了擴展GBK2312,並加入更多的漢字。GBK的編碼范圍是8140~FEFE(去掉XX7F),總共有23940個碼位,能表示21003個漢字,它的編碼是和GB2312兼容的,也就是說用GB2312編碼的漢字可以用GBK來解碼,並且不會有亂碼問題。GBK還是現如今中文Windows操作系統的系統默認編碼。

 

Unicode

在一個國家的本地化系統中出現的一個字符,通過電子郵件傳送到另外一個國家的本地化系統中,看到的就不是那個原始字符了,而是另外那個國家的一個字符或亂碼,因為計算機里面並沒有真正的字符,字符都是以數字的形式存在的,通過郵件傳送一個字符,實際上傳送的是這個字符對應的字符編碼,同一個數字在不同的國家和地區代表的很可能是不同的符號。

為了解決各個國家和地區之間各自使用不同的本地化字符編碼帶來的不便,人們將全世界所有的符號進行了統一編碼,稱之為Unicode(統一碼、萬國碼)。所有字符不再區分國家和地區,都是人類共有的符號,如"中"字在Unicode中不再是GBK中的D6D0,而是在任何地方都是4e2d,如果所有的計算機系統都使用這種編碼方式,那么4e2d這個字在任何地方都代表漢字中的"中"。Unicode編碼的字符都占用兩個字節的大小,也就是說全世界所有字符個數不會超過65536個。

當然Unicode只包含65536個字符就想包含全世界所有的字符是遠遠不夠的,所以Unicode提供了字符平面映射,鏈接地址上就是Wiki百科對於字符平面映射的解讀。另外要提一點的是,Unicode是Java和XML的基礎。

 

UTF-8和UTF-16

Unicode是一種字符集標准,而具體該標准應該如何應用到計算機中,則是另一個話題了,常用的Unicode編碼方式有兩種:

1、UTF-16。兩個字節表示Unicode轉換格式,這是定長的表示方法。也就是說不管什么字符都可以使用兩個字節表示,兩個字節是16Bit,所以叫做UTF-16。UTF-16編碼非常方便,每兩個字節表示一個字符,這個在字符串操作時大大簡化了操作。

2、UTF-8。UTF-16統一采用了兩個字節表示一個字符,雖然在表示上非常簡單,但是很大一部分字符用一個字節表示就夠了,現在需要兩個字節,存儲空間放大了一倍。UTF-8就采取了一種變長技術,每個編碼區域有不同的字碼長度,不同類型的字符可以是由1~6個字節組成。

兩種編碼方式比較,相對來說,UTF-16的編碼效率較高,從字符到字節的相互轉換可以更簡單,進行字符串操作也更好,它更適合在本地磁盤和內存之間使用,可以進行字符和字節之間的快速切換。但是UTF-16並不適合在網絡之間傳輸,因為網絡傳輸易損壞字節流,一旦字節流損壞將很難恢復,所以相比較而言UTF-8更適合網絡傳輸。另外UTF-8對ASCII字符采用單字節存儲,單個字符損壞也不會影響后面的其他字符,在編碼效率上介於GBK和UTF-16之間,所以,UTF-8在編碼效率和編碼安全性上做了平衡,是理想的中文編碼方式。

 

Java與字符編碼

Java中的字符使用的都是Unicode字符集,編碼方式為UTF-16,Java技術在通過Unicode保證跨平台特性的前提下也支持了全擴展的本地平台字符集,而顯示輸出和鍵盤輸入都是采用的本地編碼。因此,免不了二者的轉化問題。

看一個很簡單的例子:

public static void main(String[] args) throws Exception
{
    // 這里將字符串通過getBytes()方法,編碼成GB2312
    byte b[] = "大家一起來學習Java語言".getBytes("GB2312");
    File file = new File("D:/Files/encoding.txt");
    OutputStream out = new FileOutputStream(file);
    out.write(b);
    out.close();
}

看一下文件中是什么:

正常輸出,無編碼問題,但是如果這樣:

public static void main(String[] args) throws Exception
{
    // 這里將字符串通過getBytes()方法,編碼成GB2312
    byte b[] = "大家一起來學習Java語言".getBytes("ISO8859-1");
    File file = new File("D:/Files/encoding.txt");
    OutputStream out = new FileOutputStream(file);
    out.write(b);
    out.close();
}

再看一下文件中是什么:

亂碼問題就出現了,通過上述操作的完整過程分析一下原因。

要再次說明的是,Java中的String都是Unicode字符集的。Java中的各個類,對於英文字符的支持都非常好,可以正常地寫入文件中,但對於中文字符就未必了。從Java源代碼到輸入文件正確的內容,要經過"Java源代碼->Java字節碼->虛擬機->文件"幾個步驟,在上述過程中的每一步都必須正確地處理漢字的編碼,才能夠使最終有我們期望的結果。

"Java源代碼->Java字節碼",標准的Java編譯器Javac使用的字符集是系統默認的字符集,比如在中文Windows操作系統上就是GBK(上面GBK的部分已經說明過了),而在Linux操作系統上就是ISO8859-1,所以大家會發現Linux操作系統上編譯的類中源文件中的中文字符都出現了問題,解決辦法就是在編譯的時候添加encoding參數,這樣才能夠與平台無關,用法是:javac -encoding GBK。

"Java字節碼->虛擬機->文件",Java運行環境(JRE)分英文版和國際版,但只有國際版才支持非英文字符。Java開發工具包(JDK)肯定支持多國字符,但並非所有的計算機用戶都安裝了JDK。很多操作系統應用軟件為了能夠更好地支持Java,都內嵌了JRE的國際版本,為支持自己多國字符提供了方便。

問題就出"Java源代碼->Java字節碼上",這是由於JDK設置環境變量引起的。用程序看一下JDK環境變量:

public static void main(String[] args)
{
    System.getProperties().list(System.out);
}

看一下輸出的全部信息,有點長:

 1 -- listing properties --
 2 java.runtime.name=Java(TM) SE Runtime Environment
 3 sun.boot.library.path=E:\MyEclipse10\Common\binary\com.sun....
 4 java.vm.version=11.3-b02
 5 java.vm.vendor=Sun Microsystems Inc.
 6 java.vendor.url=http://java.sun.com/
 7 path.separator=;
 8 java.vm.name=Java HotSpot(TM) 64-Bit Server VM
 9 file.encoding.pkg=sun.io
10 user.country=CN
11 sun.java.launcher=SUN_STANDARD
12 sun.os.patch.level=
13 java.vm.specification.name=Java Virtual Machine Specification
14 user.dir=F:\代碼\MyEclipse\TestIO
15 java.runtime.version=1.6.0_13-b03
16 java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
17 java.endorsed.dirs=E:\MyEclipse10\Common\binary\com.sun....
18 os.arch=amd64
19 java.io.tmpdir=C:\Users\dell1\AppData\Local\Temp\
20 line.separator=
21 
22 java.vm.specification.vendor=Sun Microsystems Inc.
23 user.variant=
24 os.name=Windows Vista
25 sun.jnu.encoding=GBK
26 java.library.path=E:\MyEclipse10\Common\binary\com.sun....
27 java.specification.name=Java Platform API Specification
28 java.class.version=50.0
29 sun.management.compiler=HotSpot 64-Bit Server Compiler
30 os.version=6.2
31 user.home=C:\Users\dell1
32 user.timezone=
33 java.awt.printerjob=sun.awt.windows.WPrinterJob
34 file.encoding=GBK
35 java.specification.version=1.6
36 user.name=dell1
37 java.class.path=F:\代碼\MyEclipse\TestIO\bin
38 java.vm.specification.version=1.0
39 sun.arch.data.model=64
40 java.home=E:\MyEclipse10\Common\binary\com.sun....
41 java.specification.vendor=Sun Microsystems Inc.
42 user.language=zh
43 awt.toolkit=sun.awt.windows.WToolkit
44 java.vm.info=mixed mode
45 java.version=1.6.0_13
46 java.ext.dirs=E:\MyEclipse10\Common\binary\com.sun....
47 sun.boot.class.path=E:\MyEclipse10\Common\binary\com.sun....
48 java.vendor=Sun Microsystems Inc.
49 file.separator=\
50 java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport...
51 sun.cpu.endian=little
52 sun.io.unicode.encoding=UnicodeLittle
53 sun.desktop=windows
54 sun.cpu.isalist=amd64

注意一下34行,表明了JDK使用的是GBK字符集(GBK是GB2312上的擴展,所以用GB2312字符集當然是沒有問題的),這意味着Java對String的操作,都做了Unicode到GBK的轉換。既然JDK用的GBK編碼,那么用ISO8859-1字符集顯示GBK編碼出來的中文當然是有問題的。

這只是一個例子,在我們的應用程序中涉及I/O操作時,一般只要注意指定統一的編解碼Charset集,就不會出現亂碼問題。對有些應用程序如果不注意指定字符編碼,則在中文環境中會使用操作系統的默認編碼。如果編解碼都在中文環境中,通常也沒有問題,但還是強烈建議不要使用操作系統的默認編碼,因為這樣會使你的應用程序的編碼格式和運行時環境綁定起來,這樣在跨環境時很可能出現亂碼問題。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM