Java中文乱码详解
文章目錄
- 1 Java編碼轉(zhuǎn)換
- 1.1 String轉(zhuǎn)換圖
- 1.2 String和Unicode編碼
- 1.3 new String()的理解
- 1.4 實(shí)際例子
- 1.5 java編碼轉(zhuǎn)換過(guò)程
- 1.5.1 編輯源文件
- 1.5.2 編譯源文件
- 1.5.3 運(yùn)行編譯類
- 1.5.3.1 Console上運(yùn)行的類
- 1.5.3.2 JSPServlet類
- 1.5.3.4 數(shù)據(jù)庫(kù)部分
- 1.6 java是如何編碼解碼的
- 1.6.1 編碼&解碼
- 1.6.2 I/O操作
- 1.6.3 按字節(jié)
- 1.6.4 按字符
- 1.6.5 字節(jié)&字符轉(zhuǎn)換
- 1.6.6 內(nèi)存
- 1.6.7 編碼&編碼格式
- 1.7 javaWeb中的編碼解碼
- 1.7.1 編碼&解碼
- 1.7.2 URL方式
- 1.7.3 表單GET
- 1.7.4 表單POST
- 1.8 解決URL中文亂碼問(wèn)題
- 1.8.1 javascript
- 1.8.1.1 escape
- 1.8.1.2 encodeURI
- 1.8.1.3 encodeURIComponent()
- 1.8.1.4 一次轉(zhuǎn)碼
- 1.8.1.5 二次轉(zhuǎn)碼
- 2 計(jì)算機(jī)編碼歷史
- 2.1 認(rèn)識(shí)字符集
- 2.1.1 問(wèn)題起源
- 2.1.2 常見(jiàn)字符編碼
- 2.2 字符編碼詳解;基礎(chǔ)知識(shí) + ASCII + GB*
- 2.2.1 基礎(chǔ)知識(shí)
- 2.2.1.1 編碼
- 2.2.1.2 字符
- 2.2.1.3 字符集
- 2.2.1.4 字符編碼
- 2.2.2 ASCII
- 2.2.2.1 標(biāo)準(zhǔn)ASCII碼
- 2.2.2.2 擴(kuò)展ASCII碼
- 2.2.3 GB*編碼
- 2.2.3.1 GB2312
- 2.2.3.2 GBK
- 2.2.3.3 GB18030
- 2.2.4 Unicode編碼
- 2.2.4.1 Unicode
- 2.2.4.2 UCS
- 2.2.4.3 Little endian & Big endian
- 2.2.4.4 BOM
- 2.2.4.5 UTF-8
- 2.2.4.6 Unicode與UTF-8之間的轉(zhuǎn)換
1 Java編碼轉(zhuǎn)換
1.1 String轉(zhuǎn)換圖
圖中詳細(xì)描述了 字符串類String 與 文件File ,ByteBuffer,CharBuffer,byte[] 數(shù)組,char[]數(shù)組之間的互相轉(zhuǎn)換
1.2 String和Unicode編碼
String和Unicode編碼關(guān)系:String類始終是以Unicode編碼形式存儲(chǔ)
String.getBytes()的使用:如果不帶字符集參數(shù),就會(huì)依賴于JVM的字符集編碼,LINUX上一般為UNICODE,WINDOWS下一般為GBK.(要想改變JVM缺省字符集編碼,啟動(dòng)JVM時(shí)用選項(xiàng)-Dfile.encodeing=UTF-8。為了安全起見(jiàn),建議始終帶參數(shù)調(diào)用,例如:String s ; s.getBytes("UTF-8")。
Charset類非常好用,Charset.forName("編碼格式"),同時(shí)返回一個(gè)實(shí)例對(duì)象如:charset,那么編碼解碼如下:
- charset.encode是編碼,即把String按你指定的字符集編碼格式進(jìn)行編碼后輸出字節(jié)數(shù)組。
- charset.decode是解碼,即把一個(gè)字節(jié)數(shù)組按你指定的字符集編碼格式進(jìn)行解碼后輸出成字符串。
1.3 new String()的理解
String newStr = new String(oldStr.getBytes(), "UTF-8");java中的String類是按照unicode進(jìn)行編碼的,當(dāng)使用String(byte[] bytes, String encoding)構(gòu)造字符串時(shí),encoding所指的是bytes中的數(shù)據(jù)是按照那種方式編碼的,而不是最后產(chǎn)生的String是什么編碼方式,換句話說(shuō),是讓系統(tǒng)把bytes中的數(shù)據(jù)由encoding編碼方式轉(zhuǎn)換成unicode編碼,因?yàn)镾tring類始終是以Unicode編碼形式存儲(chǔ)。
如果不指明,bytes的編碼方式將由jdk根據(jù)操作系統(tǒng)決定。
1.4 實(shí)際例子
舉例如下:
String s = Charset.defaultCharset().displayName();String s1 = "我喜歡你,My Love";ByteBuffer bb1 = ByteBuffer.wrap(s1.getBytes("UTF-8"));for(byte bt:bb1.array()){System.out.printf("%x",bt);}//char[]用法char[] chArray={'I','L','o','v','e','你'};//CharBuffer用法CharBuffer cb = CharBuffer.wrap(chArray);//重新定位指針cb.flip();String s2= new String(chArray);//ByteBuffer用法ByteBuffer bb2 = Charset.forName("utf-8").encode(cb);// 利用Charset編碼為指定字符集ByteBuffer bb3 = Charset.forName("utf-8").encode(s1);byte [] b = bb3.array() ;// 利用Charset按指定字符集解碼為字符串ByteBuffer bb4= ByteBuffer.wrap(b);String s2 = Charset.forName("utf-8").decode(bb4).toString();使用String構(gòu)造方法和String.getBytes()做好中文字符轉(zhuǎn)碼
@Testpublic void test() {String testStr = "中";try {// 得到指定編碼的字節(jié)數(shù)組 字符串--->字節(jié)數(shù)組 6 byte[] t_iso = testStr.getBytes("ISO8859-1");byte[] t_gbk = testStr.getBytes("GBK");byte[] t_utf8 = testStr.getBytes("UTF-8");System.out.println("使用ISO解碼..." + t_iso.length);System.out.println("使用GBK解碼..." + t_gbk.length);System.out.println("使用UTF8解碼..." + t_utf8.length);// 解碼后在組裝13 String ut_iso = new String(t_iso, "ISO8859-1");String ut_gbk = new String(t_gbk, "GBK");String ut_utf8 = new String(t_utf8, "UTF-8");System.out.println("使用ISO解碼后再用ISO組裝..." + ut_iso);System.out.println("使用GBK解碼后再用GBK組裝..." + ut_gbk);System.out.println("使用UTF8解碼后再用UTF8組裝..." + ut_utf8);// 有時(shí)候要求必須是iso字符編碼類型// 可以先用GBK/UTF8編碼后,用ISO8859-1組裝成字符串,解碼時(shí)逆向即可獲得正確中文字符21 String t_utf8Toiso = new String(t_utf8, "ISO8859-1");// 將iso編碼的字符串進(jìn)行還原23 String ut_utf8Toiso = new String(t_utf8Toiso.getBytes("ISO8859-1"),"UTF-8");System.out.println("使用ISO組裝utf8編碼字符..." + t_utf8Toiso);System.out.println("使用ISO解碼utf8編碼字符..." + ut_utf8Toiso);} catch (UnsupportedEncodingException e) {e.printStackTrace();} }1.5 java編碼轉(zhuǎn)換過(guò)程
我們總是用一個(gè)java類文件和用戶進(jìn)行最直接的交互(輸入、輸出),這些交互內(nèi)容包含的文字可能會(huì)包含中文
這些過(guò)程是從宏觀上面來(lái)觀察的,了解這個(gè)肯定是不行的,我們需要真正來(lái)了解java是如何來(lái)編碼和被解碼的
1.5.1 編輯源文件
當(dāng)我們用編輯器編寫java源文件,程序文件在保存時(shí)會(huì)采用操作系統(tǒng)默認(rèn)的編碼格式(一般我們中文的操作系統(tǒng)采用的是GBK編碼格式)形成一個(gè).java文件。java源文件是采用操作系統(tǒng)默認(rèn)支持的file.encoding編碼格式保存的。下面代碼可以查看系統(tǒng)的file.encoding參數(shù)值。
System.out.println(System.getProperty("file.encoding"));
1.5.2 編譯源文件
當(dāng)我們使用javac.exe編譯我們的java文件時(shí),JDK首先會(huì)確認(rèn)它的編譯參數(shù)encoding來(lái)確定源代碼字符集,如果我們不指定該編譯參數(shù),JDK首先會(huì)獲取操作系統(tǒng)默認(rèn)的file.encoding參數(shù),然后JDK就會(huì)把我們編寫的java源程序從file.encoding編碼格式轉(zhuǎn)化為JAVA內(nèi)部默認(rèn)的UNICODE格式放入內(nèi)存中
JDK將上面編譯好的且保存在內(nèi)存中信息寫入class文件中,形成.class文件。此時(shí).class文件是Unicode編碼的,也就是說(shuō)我們常見(jiàn)的.class文件中的內(nèi)容無(wú)論是中文字符還是英文字符,他們都已經(jīng)轉(zhuǎn)換為Unicode編碼格式了。
在這一步中對(duì)JSP源文件的處理方式有點(diǎn)兒不同:WEB容器調(diào)用JSP編譯器,JSP編譯器首先會(huì)查看JSP文件是否設(shè)置了文件編碼格式,如果沒(méi)有設(shè)置則JSP編譯器會(huì)調(diào)用調(diào)用JDK采用默認(rèn)的編碼方式將JSP文件轉(zhuǎn)化為臨時(shí)的servlet類,然后再編譯為.class文件并保持到臨時(shí)文件夾中。
1.5.3 運(yùn)行編譯類
運(yùn)行編譯的類:在這里會(huì)存在一下幾種情況
直接在console上運(yùn)行,JSP/Servlet類,java類與數(shù)據(jù)庫(kù)之間。
這三種情況每種情況的方式都會(huì)不同,
1.5.3.1 Console上運(yùn)行的類
這種情況下,JVM首先會(huì)把保存在操作系統(tǒng)中的class文件讀入到內(nèi)存中,這個(gè)時(shí)候內(nèi)存中class文件編碼格式為Unicode,然后JVM運(yùn)行它。如果需要用戶輸入信息,則會(huì)采用file.encoding編碼格式對(duì)用戶輸入的信息進(jìn)行編碼同時(shí)轉(zhuǎn)換為Unicode編碼格式保存到內(nèi)存中。程序運(yùn)行后,將產(chǎn)生的結(jié)果再轉(zhuǎn)化為file.encoding格式返回給操作系統(tǒng)并輸出到界面去。整個(gè)流程如下:
在上面整個(gè)流程中,凡是涉及的編碼轉(zhuǎn)換都不能出現(xiàn)錯(cuò)誤,否則將會(huì)產(chǎn)生亂碼。
1.5.3.2 JSPServlet類
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="GBK" %>在上面代碼中有兩個(gè)地方存在編碼:pageEncoding、contentType的charset。其中pageEncoding是jsp文件本身的編碼,而contentType的charset是指服務(wù)器發(fā)送給客戶端時(shí)的內(nèi)容編碼
JVM將JSP編譯為.jsp文件。在這個(gè)過(guò)程中pageEncoding就起到作用了,JVM首先會(huì)獲取pageEncoding的值,如果該值存在則采用它設(shè)定的編碼來(lái)編譯,否則則采用file.encoding編碼來(lái)編譯。
JVM將.java文件轉(zhuǎn)換為.class文件。在這個(gè)過(guò)程就與任何編碼的設(shè)置都沒(méi)有關(guān)系了,不管JSP采用了什么樣的編碼格式都將無(wú)效。經(jīng)過(guò)這個(gè)階段后.jsp文件就轉(zhuǎn)換成了統(tǒng)一的Unicode格式的.class文件了。
后臺(tái)經(jīng)過(guò)業(yè)務(wù)邏輯處理后將產(chǎn)生的結(jié)果輸出到客戶端。在這個(gè)過(guò)程中contentType的charset就發(fā)揮了功效。如果設(shè)置了charset則瀏覽器就會(huì)使用指定的編碼格式進(jìn)行解碼,否則采用默認(rèn)的ISO-8859-1編碼格式進(jìn)行解碼處理。
傳輸時(shí)的編碼格式
當(dāng)用戶請(qǐng)求Servlet時(shí),WEB容器會(huì)調(diào)用它的JVM來(lái)運(yùn)行Servlet。首先JVM會(huì)把servlet的class加載到內(nèi)存中去,內(nèi)存中的servlet代碼是Unicode編碼格式的。然后JVM在內(nèi)存中運(yùn)行該Servlet,在運(yùn)行過(guò)程中如果需要接受從客戶端傳遞過(guò)來(lái)的數(shù)據(jù)(如表單和URL傳遞的數(shù)據(jù)),則WEB容器會(huì)接受傳入的數(shù)據(jù),在接收過(guò)程中如果程序設(shè)定了傳入?yún)?shù)的的編碼則采用設(shè)定的編碼格式,如果沒(méi)有設(shè)置則采用默認(rèn)的ISO-8859-1編碼格式,接收的數(shù)據(jù)后JVM會(huì)將這些數(shù)據(jù)進(jìn)行編碼格式轉(zhuǎn)換為Unicode并且存入到內(nèi)存中。運(yùn)行Servlet后產(chǎn)生輸出結(jié)果,同時(shí)這些輸出結(jié)果的編碼格式仍然為Unicode。緊接著WEB容器會(huì)將產(chǎn)生的Unicode編碼格式的字符串直接發(fā)送置客戶端,如果程序指定了輸出時(shí)的編碼格式,則按照指定的編碼格式輸出到瀏覽器,否則采用默認(rèn)的ISO-8859-1編碼格式。整個(gè)過(guò)程流程圖如下:
1.5.3.4 數(shù)據(jù)庫(kù)部分
我們知道java程序與數(shù)據(jù)庫(kù)的連接都是通過(guò)JDBC驅(qū)動(dòng)程序來(lái)連接的,而JDBC驅(qū)動(dòng)程序默認(rèn)的是ISO-8859-1編碼格式的,也就是說(shuō)我們通過(guò)java程序向數(shù)據(jù)庫(kù)傳遞數(shù)據(jù)時(shí),JDBC首先會(huì)將Unicode編碼格式的數(shù)據(jù)轉(zhuǎn)換為ISO-8859-1的編碼格式,然后在存儲(chǔ)在數(shù)據(jù)庫(kù)中,即在數(shù)據(jù)庫(kù)保存數(shù)據(jù)時(shí),默認(rèn)格式為ISO-8859-1
1.6 java是如何編碼解碼的
1.6.1 編碼&解碼
在java中主要有四個(gè)場(chǎng)景需要進(jìn)行編碼解碼操作:
I/O操作,內(nèi)存,數(shù)據(jù)庫(kù),javaWeb
下面主要介紹前面兩種場(chǎng)景,數(shù)據(jù)庫(kù)部分只要設(shè)置正確編碼格式就不會(huì)有什么問(wèn)題,javaWeb場(chǎng)景過(guò)多需要了解URL、get、POST的編碼,servlet的解碼
1.6.2 I/O操作
所謂亂碼問(wèn)題無(wú)非就是轉(zhuǎn)碼過(guò)程中編碼格式的不統(tǒng)一產(chǎn)生的,比如編碼時(shí)采用UTF-8,解碼采用GBK,但最根本的原因是字符到字節(jié)或者字節(jié)到字符的轉(zhuǎn)換出問(wèn)題了,而這中情況的轉(zhuǎn)換最主要的場(chǎng)景就是I/O操作的時(shí)候。當(dāng)然I/O操作主要包括網(wǎng)絡(luò)I/O(也就是javaWeb)和磁盤I/O。
首先我們先看I/O的編碼操作
InputStream為字節(jié)輸入流的所有類的超類,Reader為讀取字符流的抽象類。java讀取文件的方式分為按字節(jié)流讀取和按字符流讀取,其中InputStream、Reader是這兩種讀取方式的超類。
1.6.3 按字節(jié)
一般都是使用InputStream.read()方法在數(shù)據(jù)流中讀取字節(jié)(read()每次都只讀取一個(gè)字節(jié),效率非常慢,我們一般都是使用read(byte[])),然后保存在一個(gè)byte[]數(shù)組中,最后轉(zhuǎn)換為String。在我們讀取文件時(shí),讀取字節(jié)的編碼取決于文件所使用的編碼格式,而在轉(zhuǎn)換為String過(guò)程中也會(huì)涉及到編碼的問(wèn)題,如果兩者之間的編碼格式不同可能會(huì)出現(xiàn)問(wèn)題。例如存在一個(gè)問(wèn)題test.txt編碼格式為UTF-8,那么通過(guò)字節(jié)流讀取文件時(shí)所獲得的數(shù)據(jù)流編碼格式就是UTF-8,而我們?cè)谵D(zhuǎn)化成String過(guò)程中如果不指定編碼格式,則默認(rèn)使用系統(tǒng)編碼格式(GBK)來(lái)解碼操作,由于兩者編碼格式不一致,那么在構(gòu)造String過(guò)程肯定會(huì)產(chǎn)生亂碼,如下
File file = new File("C:\\test.txt");InputStream input = new FileInputStream(file);StringBuffer buffer = new StringBuffer();byte[] bytes = new byte[1024];for(int n ; (n = input.read(bytes))!=-1 ; ){buffer.append(new String(bytes,0,n));}System.out.println(buffer);輸出結(jié)果:锘挎垜鏄?cm要想不出現(xiàn)亂碼,在構(gòu)造String過(guò)程中指定編碼格式,使得編碼解碼時(shí)兩者編碼格式保持一致即可:
buffer.append(new String(bytes,0,n,"UTF-8"));
1.6.4 按字符
其實(shí)字符流可以看做是一種包裝流,它的底層還是采用字節(jié)流來(lái)讀取字節(jié),然后它使用指定的編碼方式將讀取字節(jié)解碼為字符。在java中Reader是讀取字符流的超類。所以從底層上來(lái)看按字節(jié)讀取文件和按字符讀取沒(méi)什么區(qū)別。在讀取的時(shí)候字符讀取每次是讀取留個(gè)字節(jié),字節(jié)流每次讀取一個(gè)字節(jié)。
1.6.5 字節(jié)&字符轉(zhuǎn)換
字節(jié)轉(zhuǎn)換為字符一定少不了InputStreamReader。API解釋如下:InputStreamReader 是字節(jié)流通向字符流的橋梁:它使用指定的 charset讀取字節(jié)并將其解碼為字符。它使用的字符集可以由名稱指定或顯式給定,或者可以接受平臺(tái)默認(rèn)的字符集。 每次調(diào)用 InputStreamReader 中的一個(gè)read()方法都會(huì)導(dǎo)致從底層輸入流讀取一個(gè)或多個(gè)字節(jié)。要啟用從字節(jié)到字符的有效轉(zhuǎn)換,可以提前從底層流讀取更多的字節(jié),使其超過(guò)滿足當(dāng)前讀取操作所需的字節(jié)。API解釋非常清楚,InputStreamReader在底層讀取文件時(shí)仍然采用字節(jié)讀取,讀取字節(jié)后它需要根據(jù)一個(gè)指定的編碼格式來(lái)解析為字符,如果沒(méi)有指定編碼格式則采用系統(tǒng)默認(rèn)編碼格式。
String file = "C:\\test.txt"; String charset = "UTF-8"; // 寫字符換轉(zhuǎn)成字節(jié)流FileOutputStream outputStream = new FileOutputStream(file); OutputStreamWriter writer = new OutputStreamWriter(outputStream, charset); try { writer.write("我是 cm"); } finally { writer.close(); } // 讀取字節(jié)轉(zhuǎn)換成字符FileInputStream inputStream = new FileInputStream(file); InputStreamReader reader = new InputStreamReader(inputStream, charset); StringBuffer buffer = new StringBuffer(); char[] buf = new char[64]; int count = 0; try { while ((count = reader.read(buf)) != -1) { buffer.append(buf, 0, count); } } finally { reader.close(); }System.out.println(buffer);1.6.6 內(nèi)存
首先我們看下面這段簡(jiǎn)單的代碼
String s = "我是 cm"; byte[] bytes = s.getBytes(); String s1 = new String(bytes,"GBK"); String s2 = new String(bytes);在這段代碼中我們看到了三處編碼轉(zhuǎn)換過(guò)程(一次編碼,兩次解碼)。先看String.getTytes():
public byte[] getBytes() {return StringCoding.encode(value, 0, value.length);}內(nèi)部調(diào)用StringCoding.encode()方法操作:
static byte[] encode(char[] ca, int off, int len) {String csn = Charset.defaultCharset().name();try {// use charset name encode() variant which provides caching.return encode(csn, ca, off, len);} catch (UnsupportedEncodingException x) {warnUnsupportedCharset(csn);}try {return encode("ISO-8859-1", ca, off, len);} catch (UnsupportedEncodingException x) {// If this code is hit during VM initialization, MessageUtils is// the only way we will be able to get any kind of error message.MessageUtils.err("ISO-8859-1 charset not available: " + x.toString());// If we can not find ISO-8859-1 (a required encoding) then things// are seriously wrong with the installation.System.exit(1);return null;}}encode(char[] paramArrayOfChar, int paramInt1, int paramInt2)方法首先調(diào)用系統(tǒng)的默認(rèn)編碼格式,如果沒(méi)有指定編碼格式則默認(rèn)使用ISO-8859-1編碼格式進(jìn)行編碼操作,進(jìn)一步深入如下:String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
同樣的方法可以看到new String 的構(gòu)造函數(shù)內(nèi)部是調(diào)用StringCoding.decode()方法:
decode方法和encode對(duì)編碼格式的處理是一樣的。
對(duì)于以上兩種情況我們只需要設(shè)置統(tǒng)一的編碼格式一般都不會(huì)產(chǎn)生亂碼問(wèn)題。
1.6.7 編碼&編碼格式
首先先看看java編碼類圖
首先根據(jù)指定的chart設(shè)置ChartSet類,然后根據(jù)ChartSet創(chuàng)建ChartSetEncoder對(duì)象,最后再調(diào)用 CharsetEncoder.encode 對(duì)字符串進(jìn)行編碼,不同的編碼類型都會(huì)對(duì)應(yīng)到一個(gè)類中,實(shí)際的編碼過(guò)程是在這些類中完成的。下面時(shí)序圖展示詳細(xì)的編碼過(guò)程:
通過(guò)這編碼的類圖和時(shí)序圖可以了解編碼的詳細(xì)過(guò)程。下面將通過(guò)一段簡(jiǎn)單的代碼對(duì)ISO-8859-1、GBK、UTF-8編碼
通過(guò)程序我們可以看到“我是 cm”的結(jié)果為:
char[]:6211 662f 20 63 6d
ISO-8859-1:3F 3F 20 63 6D
GBK:CE D2 CA C7 20 63 6D
UTF-8:E6 88 91 E6 98 AF 20 63 6D
圖如下:
1.7 javaWeb中的編碼解碼
1.7.1 編碼&解碼
用戶想服務(wù)器發(fā)送一個(gè)HTTP請(qǐng)求,需要編碼的地方有url、cookie、parameter,經(jīng)過(guò)編碼后服務(wù)器接受HTTP請(qǐng)求,解析HTTP請(qǐng)求,然后對(duì)url、cookie、parameter進(jìn)行解碼。在服務(wù)器進(jìn)行業(yè)務(wù)邏輯處理過(guò)程中可能需要讀取數(shù)據(jù)庫(kù)、本地文件或者網(wǎng)絡(luò)中的其他文件等等,這些過(guò)程都需要進(jìn)行編碼解碼。當(dāng)處理完成后,服務(wù)器將數(shù)據(jù)進(jìn)行編碼后發(fā)送給客戶端,瀏覽器經(jīng)過(guò)解碼后顯示給用戶。在這個(gè)整個(gè)過(guò)程中涉及的編碼解碼的地方較多,其中最容易出現(xiàn)亂碼的位置就在于服務(wù)器與客戶端進(jìn)行交互的過(guò)程。
上面整個(gè)過(guò)程可以概括成這樣,頁(yè)面編碼數(shù)據(jù)傳遞給服務(wù)器,服務(wù)器對(duì)獲得的數(shù)據(jù)進(jìn)行解碼操作,經(jīng)過(guò)一番業(yè)務(wù)邏輯處理后將最終結(jié)果編碼處理后傳遞給客戶端,客戶端解碼展示給用戶。所以下面我就請(qǐng)求對(duì)javaweb的編碼&解碼進(jìn)行闡述。
客戶端想服務(wù)器發(fā)送請(qǐng)求無(wú)非就通過(guò)四中情況:
- URL方式直接訪問(wèn)。
- 頁(yè)面鏈接。
- 表單get提交
- 表單post提交
1.7.2 URL方式
對(duì)于URL,如果該URL中全部都是英文的那倒是沒(méi)有什么問(wèn)題,如果有中文就要涉及到編碼了。如何編碼?根據(jù)什么規(guī)則來(lái)編碼?又如何來(lái)解碼呢,首先看URL的組成部分:
在這URL中瀏覽器將會(huì)對(duì)path和parameter進(jìn)行編碼操作。為了更好地解釋編碼過(guò)程,使用如下URL
http://127.0.0.1:8080/perbank/我是cm?name=我是cm
將以上地址輸入到瀏覽器URL輸入框中,通過(guò)查看http 報(bào)文頭信息我們可以看到瀏覽器是如何進(jìn)行編碼的。下面是IE、Firefox、Chrome三個(gè)瀏覽器的編碼情況:
可以看到各大瀏覽器對(duì)我是的編碼情況如下:
| Firefox | E6 88 91 E6 98 AF | E6 88 91 E6 98 AF |
| Chrome | E6 88 91 E6 98 AF | E6 88 91 E6 98 AF |
| IE | E6 88 91 E6 98 AF | CE D2 CA C7 |
對(duì)于path部分Firefox、chrome、IE都是采用UTF-8編碼格式,對(duì)于Query String部分Firefox、chrome采用UTF-8,IE采用GBK。至于為什么會(huì)加上%,這是因?yàn)閁RL的編碼規(guī)范規(guī)定瀏覽器將ASCII字符非 ASCII 字符按照某種編碼格式編碼成16進(jìn)制數(shù)字然后將每個(gè) 16 進(jìn)制表示的字節(jié)前加上%。
當(dāng)然對(duì)于不同的瀏覽器,相同瀏覽器不同版本,不同的操作系統(tǒng)等環(huán)境都會(huì)導(dǎo)致編碼結(jié)果不同,上表某一種情況,對(duì)于URL編碼規(guī)則下任何結(jié)論都是過(guò)早的。由于各大瀏覽器、各個(gè)操作系統(tǒng)對(duì)URL的URI、QueryString編碼都可能存在不同,這樣對(duì)服務(wù)器的解碼勢(shì)必會(huì)造成很大的困擾,下面我們將tomcat,看tomcat是如何對(duì)URL進(jìn)行解碼操作的。
解析請(qǐng)求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中,這個(gè)方法把傳過(guò)來(lái)的 URL 的 byte[] 設(shè)置到 org.apache.coyote.Request 的相應(yīng)的屬性中。這里的 URL 仍然是 byte 格式,轉(zhuǎn)成 char 是在 org.apache.catalina.connector.CoyoteAdapter 的 convertURI 方法中完成的:
從上面的代碼可知,對(duì)URI的解碼操作是首先獲取Connector的解碼集,該配置在server.xml中
<Connector URIEncoding="utf-8" />如果沒(méi)有定義則會(huì)采用默認(rèn)編碼ISO-8859-1來(lái)解析。
對(duì)于Query String部分,我們知道無(wú)論我們是通過(guò)get方式還是POST方式提交,所有的參數(shù)都是保存在Parameters,然后我們通過(guò)request.getParameter,解碼工作就是在第一次調(diào)用getParameter方法時(shí)進(jìn)行的。在getParameter方法內(nèi)部它調(diào)用org.apache.catalina.connector.Request 的 parseParameters 方法,這個(gè)方法將會(huì)對(duì)傳遞的參數(shù)進(jìn)行解碼。下面代碼只是parseParameters方法的一部分:
從上面代碼可以看出對(duì)query String的解碼格式要么采用設(shè)置的ChartSet要么采用默認(rèn)的解碼格式ISO-8859-1。注意這個(gè)設(shè)置的ChartSet是在 http Header中定義的ContentType,同時(shí)如果我們需要改指定屬性生效,還需要進(jìn)行如下配置:
<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true"/>
上面部分詳細(xì)介紹了URL方式請(qǐng)求的編碼解碼過(guò)程。其實(shí)對(duì)于我們而言,我們更多的方式是通過(guò)表單的形式來(lái)提交。
1.7.3 表單GET
我們知道通過(guò)URL方式提交數(shù)據(jù)是很容易產(chǎn)生亂碼問(wèn)題的,所以我們更加傾向于通過(guò)表單形式。當(dāng)用戶點(diǎn)擊submit提交表單時(shí),瀏覽器會(huì)更加設(shè)定的編碼來(lái)編碼數(shù)據(jù)傳遞給服務(wù)器。通過(guò)GET方式提交的數(shù)據(jù)都是拼接在URL后面來(lái)提交的,所以tomcat服務(wù)器在進(jìn)行解碼過(guò)程中URIEncoding就起到作用了。tomcat服務(wù)器會(huì)根據(jù)設(shè)置的URIEncoding來(lái)進(jìn)行解碼,如果沒(méi)有設(shè)置則會(huì)使用默認(rèn)的ISO-8859-1來(lái)解碼。假如我們?cè)陧?yè)面將編碼設(shè)置為UTF-8,而URIEncoding設(shè)置的不是或者沒(méi)有設(shè)置,那么服務(wù)器進(jìn)行解碼時(shí)就會(huì)產(chǎn)生亂碼。這個(gè)時(shí)候我們一般可以通過(guò)new String(request.getParameter(“name”).getBytes(“iso-8859-1″),”utf-8″) 的形式來(lái)獲取正確數(shù)據(jù)。
1.7.4 表單POST
對(duì)于POST方式,它采用的編碼也是由頁(yè)面來(lái)決定的即contentType。當(dāng)我通過(guò)點(diǎn)擊頁(yè)面的submit按鈕來(lái)提交表單時(shí),瀏覽器首先會(huì)根據(jù)contentType的charset編碼格式來(lái)對(duì)POST表單的參數(shù)進(jìn)行編碼然后提交給服務(wù)器,在服務(wù)器端同樣也是用contentType中設(shè)置的字符集來(lái)進(jìn)行解碼(這里與get方式就不同了),這就是通過(guò)POST表單提交的參數(shù)一般而言都不會(huì)出現(xiàn)亂碼問(wèn)題。當(dāng)然這個(gè)字符集編碼我們是可以自己設(shè)定的:request.setCharacterEncoding(charset)
1.8 解決URL中文亂碼問(wèn)題
1.8.1 javascript
使用javascript編碼不給瀏覽器插手的機(jī)會(huì),編碼之后再向服務(wù)器發(fā)送請(qǐng)求,然后在服務(wù)器中解碼。在掌握該方法的時(shí)候,我們需要料及javascript編碼的三個(gè)方法:escape()、encodeURI()、encodeURIComponent()。
1.8.1.1 escape
采用SIO Latin字符集對(duì)指定的字符串進(jìn)行編碼。所有非ASCII字符都會(huì)被編碼為%xx格式的字符串,其中xx表示該字符在字符集中所對(duì)應(yīng)的16進(jìn)制數(shù)字。例如,格式對(duì)應(yīng)的編碼為%20。它對(duì)應(yīng)的解碼方法為unescape()。
事實(shí)上escape()不能直接用于URL編碼,它的真正作用是返回一個(gè)字符的Unicode編碼值。比如上面我是cm的結(jié)果為%u6211%u662Fcm,其中我對(duì)應(yīng)的編碼為6211,是的編碼為662F,cm編碼為cm。
注意,escape()不對(duì)+編碼。但是我們知道,網(wǎng)頁(yè)在提交表單的時(shí)候,如果有空格,則會(huì)被轉(zhuǎn)化為+字符。服務(wù)器處理數(shù)據(jù)的時(shí)候,會(huì)把+號(hào)處理成空格。所以,使用的時(shí)候要小心
1.8.1.2 encodeURI
對(duì)整個(gè)URL進(jìn)行編碼,它采用的是UTF-8格式輸出編碼后的字符串。不過(guò)encodeURI除了ASCII編碼外對(duì)于一些特殊的字符也不會(huì)進(jìn)行編碼如:! @ # $& * ( ) = : / ; ? + ‘
1.8.1.3 encodeURIComponent()
把URI字符串采用UTF-8編碼格式轉(zhuǎn)化成escape格式的字符串。相對(duì)于encodeURI,encodeURIComponent會(huì)更加強(qiáng)大,它會(huì)對(duì)那些在encodeURI()中不被編碼的符號(hào)(; / ? : @ & = + $ , #)統(tǒng)統(tǒng)會(huì)被編碼。但是encodeURIComponent只會(huì)對(duì)URL的組成部分進(jìn)行個(gè)別編碼,而不用于對(duì)整個(gè)URL進(jìn)行編碼。對(duì)應(yīng)解碼函數(shù)方法decodeURIComponent。
當(dāng)然我們一般都是使用encodeURI方來(lái)進(jìn)行編碼操作。所謂的javascript兩次編碼后臺(tái)兩次解碼就是使用該方法。javascript解決該問(wèn)題有一次轉(zhuǎn)碼、兩次轉(zhuǎn)碼兩種解決方法
1.8.1.4 一次轉(zhuǎn)碼
javascript轉(zhuǎn)碼:
var url = '<s:property value="webPath" />/ShowMoblieQRCode.servlet?name=我是cm'; window.location.href = encodeURI(url);轉(zhuǎn)碼后的URL:http://127.0.0.1:8080/perbank/ShowMoblieQRCode.servlet?name=%E6%88%91%E6%98%AFcm 后臺(tái)處理: String name = request.getParameter("name");System.out.println("前臺(tái)傳入?yún)?shù):" + name);name = new String(name.getBytes("ISO-8859-1"),"UTF-8");System.out.println("經(jīng)過(guò)解碼后參數(shù):" + name);輸出結(jié)果: 前臺(tái)傳入?yún)?shù):??????cm 經(jīng)過(guò)解碼后參數(shù):我是cm1.8.1.5 二次轉(zhuǎn)碼
var url = '<s:property value="webPath" />/ShowMoblieQRCode.servlet?name=我是cm'; window.location.href = encodeURI(encodeURI(url));轉(zhuǎn)碼后的url:http://127.0.0.1:8080/perbank/ShowMoblieQRCode.servlet?name=%25E6%2588%2591%25E6%2598%25AFcm 后臺(tái)處理:String name = request.getParameter("name");System.out.println("前臺(tái)傳入?yún)?shù):" + name);name = URLDecoder.decode(name,"UTF-8");System.out.println("經(jīng)過(guò)解碼后參數(shù):" + name);輸出結(jié)果: 前臺(tái)傳入?yún)?shù):E68891E698AFcm 經(jīng)過(guò)解碼后參數(shù):我是cm2 計(jì)算機(jī)編碼歷史
2.1 認(rèn)識(shí)字符集
2.1.1 問(wèn)題起源
對(duì)于計(jì)算機(jī)而言,它僅認(rèn)識(shí)兩個(gè)0和1,不管是在內(nèi)存中還是外部存儲(chǔ)設(shè)備上,我們所看到的文字、圖片、視頻等等數(shù)據(jù)在計(jì)算機(jī)中都是已二進(jìn)制形式存在的。不同字符對(duì)應(yīng)二進(jìn)制數(shù)的規(guī)則,就是字符的編碼。字符編碼的集合稱為字符集。
在早期的計(jì)算機(jī)系統(tǒng)中,使用的字符是非常少的,他們只包括26個(gè)英文字母、數(shù)字符號(hào)和一些常用符號(hào),對(duì)于這些字符進(jìn)行編碼,用1個(gè)字節(jié)就足夠了,但是隨著計(jì)算機(jī)的不斷發(fā)展,為了適應(yīng)全世界其他各國(guó)民族的語(yǔ)言,這些少得可憐的字符編碼肯定是不夠的。于是人們提出了UNICODE編碼,它采用雙字節(jié)編碼,兼容英文字符和其他國(guó)家民族的雙字節(jié)字符編碼。
每個(gè)國(guó)家為了統(tǒng)一編碼都會(huì)規(guī)定該國(guó)家/地區(qū)計(jì)算機(jī)信息交換用的字符集編碼,為了解決本地字符信息的計(jì)算機(jī)處理,于是出現(xiàn)了各種本地化版本,引進(jìn)LANG, Codepage 等概念。
現(xiàn)在大部分具有國(guó)際化特征的軟件核心字符處理都是以 Unicode 為基礎(chǔ)的,在軟件運(yùn)行時(shí)根據(jù)當(dāng)時(shí)的 Locale/Lang/Codepage 設(shè)置確定相應(yīng)的本地字符編碼設(shè)置,并依此處理本地字符。在處理過(guò)程中需要實(shí)現(xiàn) Unicode 和本地字符集的相互轉(zhuǎn)換。
java內(nèi)部采用的就是Unicode編碼,所以在java運(yùn)行的過(guò)程中就必然存在從Unicode編碼與相應(yīng)的計(jì)算機(jī)操作系統(tǒng)或者瀏覽器支持的編碼格式相互轉(zhuǎn)化的過(guò)程,這個(gè)轉(zhuǎn)換的過(guò)程有一系列的步驟,如果某個(gè)步驟出現(xiàn)錯(cuò)誤,則輸出的文字就會(huì)是亂碼。所以產(chǎn)生java亂碼的問(wèn)題就在于JVM與對(duì)應(yīng)的操作系統(tǒng)/瀏覽器進(jìn)行編碼格式轉(zhuǎn)換時(shí)出現(xiàn)了錯(cuò)誤。
其實(shí)解決 JAVA 程序中的漢字編碼問(wèn)題的方法往往很簡(jiǎn)單,但理解其背后的原因,定位問(wèn)題,還需要了解現(xiàn)有的漢字編碼和編碼轉(zhuǎn)換。
2.1.2 常見(jiàn)字符編碼
計(jì)算機(jī)要準(zhǔn)確的處理各種字符集文字,需要進(jìn)行字符編碼,以便計(jì)算機(jī)能夠識(shí)別和存儲(chǔ)各種文字。常見(jiàn)的字符編碼主要包括:ASCII編碼、GBK編碼、Unicode
2.2 字符編碼詳解;基礎(chǔ)知識(shí) + ASCII + GB*
2.2.1 基礎(chǔ)知識(shí)
在了解各種字符集之前我們需要了解一些最基礎(chǔ)的知識(shí),如:編碼、字符、字符集、字符編碼基礎(chǔ)知識(shí)。
2.2.1.1 編碼
計(jì)算機(jī)中存儲(chǔ)的信息都是用二進(jìn)制表示的,我們?cè)谄聊簧纤吹轿淖帧D片等都是通過(guò)二進(jìn)制轉(zhuǎn)換的結(jié)果。編碼是信息從一種形式或格式轉(zhuǎn)換為另一種形式的過(guò)程,通俗點(diǎn)講就是就是將我們看到的文字、圖片等信息按照某種規(guī)則存儲(chǔ)在計(jì)算機(jī)中,例如‘c’在計(jì)算機(jī)中怎么表達(dá),‘陳’在計(jì)算機(jī)中怎么表達(dá),這個(gè)過(guò)程就稱之為編碼。解碼是編碼的逆過(guò)程,它是將存儲(chǔ)在計(jì)算機(jī)的二進(jìn)制轉(zhuǎn)換為我們可以看到的文字、圖片等信息,它體現(xiàn)的是視覺(jué)上的刺激。
n位二進(jìn)制數(shù)可以組合成2的n次方個(gè)不同的信息,給每個(gè)信息規(guī)定一個(gè)具體碼組,這種過(guò)程也叫編碼。
在編碼和解碼中,他們就如加密、解密一般,他們一定會(huì)遵循某個(gè)規(guī)則,即y = f(x),那么x = f(y);否則在解密過(guò)程就會(huì)導(dǎo)致‘a(chǎn)’解析成‘b’或者亂碼。
2.2.1.2 字符
字符是可使用多種不同字符方案或代碼頁(yè)來(lái)表示的抽象實(shí)體,它是一個(gè)單位的字形、類字形單位或符號(hào)的基本信息,也是各種文字和符號(hào)的總稱,包括各國(guó)家文字、標(biāo)點(diǎn)符號(hào)、圖形符號(hào)、數(shù)字等。
字符是指計(jì)算機(jī)中使用的字母、數(shù)字、字和符號(hào),包括:1、2、3、A、B、C、~!·#¥%……—*()——+等等。在 ASCII 編碼中,一個(gè)英文字母字符存儲(chǔ)需要1個(gè)字節(jié)。在 GB 2312 編碼或 GBK 編碼中,一個(gè)漢字字符存儲(chǔ)需要2個(gè)字節(jié)。在UTF-8編碼中,一個(gè)英文字母字符存儲(chǔ)需要1個(gè)字節(jié),一個(gè)漢字字符儲(chǔ)存需要3到4個(gè)字節(jié)。在UTF-16編碼中,一個(gè)英文字母字符或一個(gè)漢字字符存儲(chǔ)都需要2個(gè)字節(jié)(Unicode擴(kuò)展區(qū)的一些漢字存儲(chǔ)需要4個(gè)字節(jié))。在UTF-32編碼中,世界上任何字符的存儲(chǔ)都需要4個(gè)字節(jié)
2.2.1.3 字符集
字符是各種文字和符號(hào)的總稱,而字符集則是多個(gè)字符的集合,字符集種類較多,每個(gè)字符集包含的字符個(gè)數(shù)不同。而計(jì)算機(jī)要準(zhǔn)確的處理各種字符集文字,需要進(jìn)行字符編碼,以便計(jì)算機(jī)能夠識(shí)別和存儲(chǔ)各種文字。
常見(jiàn)字符集名稱:ASCII字符集、GB2312字符集、BIG5字符集、 GB18030字符集、Unicode字符集等。
2.2.1.4 字符編碼
計(jì)算機(jī)中的信息包括數(shù)據(jù)信息和控制信息,然而不管是那種信息,他們都是以二進(jìn)制編碼的方式存入計(jì)算機(jī)中,但是他們是怎么展示在屏幕上的呢?同時(shí)在展現(xiàn)過(guò)程中如何才能保證他們不出錯(cuò)?這個(gè)時(shí)候字符編碼就起到了重要作用!字符編碼是一套規(guī)則,一套建立在符合集合與數(shù)字系統(tǒng)之間的對(duì)應(yīng)關(guān)系之上的規(guī)則,它是信息處理的基本技術(shù)。
使用字符編碼這套規(guī)則能夠?qū)ψ匀徽Z(yǔ)言的字符的一個(gè)集合(如字母表或音節(jié)表),與其他東西的一個(gè)集合(如號(hào)碼或電脈沖)進(jìn)行配對(duì)
2.2.2 ASCII
2.2.2.1 標(biāo)準(zhǔn)ASCII碼
ASCII,American Standard Code for Information Interchange,是基于拉丁字母的一套電腦編碼系統(tǒng),主要用于顯示現(xiàn)代英語(yǔ)和其他西歐語(yǔ)言。它是現(xiàn)今最通用的單字節(jié)編碼系統(tǒng)。
ASCII碼使用指定的7位或者8位二進(jìn)制數(shù)字組合表示128或者256種可能的字符。標(biāo)準(zhǔn)的ASCII編碼使用的是7(2^7 = 128)位二進(jìn)制數(shù)來(lái)表示所有的大小寫字母、數(shù)字和標(biāo)點(diǎn)符號(hào)已經(jīng)一些特殊的控制字符,最前面的一位統(tǒng)一規(guī)定為0。其中0~31及127(共33個(gè))是控制字符或通信專用字符,32~126(共95個(gè))是字符(32是空格),其中48~57為0到9十個(gè)阿拉伯?dāng)?shù)字,65~90為26個(gè)大寫英文字母,97~122號(hào)為26個(gè)小寫英文字母,其余為一些標(biāo)點(diǎn)符號(hào)、運(yùn)算符號(hào)等
前面提過(guò)標(biāo)準(zhǔn)的ASCII碼是使用七位來(lái)表示字符的,而最高位(b7)則是用作奇偶校驗(yàn)的。所謂奇偶校驗(yàn),是指在代碼傳送過(guò)程中用來(lái)檢驗(yàn)是否出現(xiàn)錯(cuò)誤的一種方法,一般分奇校驗(yàn)和偶校驗(yàn)兩種。
奇校驗(yàn)規(guī)定:正確的代碼一個(gè)字節(jié)中1的個(gè)數(shù)必須是奇數(shù),若非奇數(shù),則在最高位b7添1;
偶校驗(yàn)規(guī)定:正確的代碼一個(gè)字節(jié)中1的個(gè)數(shù)必須是偶數(shù),若非偶數(shù),則在最高位b7添1。
2.2.2.2 擴(kuò)展ASCII碼
標(biāo)準(zhǔn)的ASCII是用七位來(lái)表示的,那么它的缺陷就非常明顯了:只能顯示26個(gè)基本拉丁字母、阿拉伯?dāng)?shù)目字和英式標(biāo)點(diǎn)符號(hào),基本上只能應(yīng)用于現(xiàn)代美國(guó)英語(yǔ),對(duì)于其他國(guó)家,128個(gè)字符肯定不夠。于是,這些歐洲國(guó)家決定利用字節(jié)中閑置的最高位編入新的符號(hào),這樣一來(lái),可以表達(dá)的字符數(shù)最多就為256個(gè),但是隨著產(chǎn)生的問(wèn)題也就來(lái)了:不同的國(guó)家有不同的字母,可能同一個(gè)編碼在不同的國(guó)家所表示的字符不同。但是不管怎么樣,在這些編碼中0~127所表示的字符肯定是一樣的,不一樣的也只是128~255這一段。
8位ASCII在歐洲國(guó)家表現(xiàn)的不盡人意,那么在其他國(guó)家就更加不用說(shuō)了,我們擁有五千年歷史文化的中華名族所包含的漢字多大10多萬(wàn),不知道是多少個(gè)256。所以一個(gè)字節(jié)8位表示的256個(gè)字符肯定是不夠的,那么兩個(gè)字節(jié)呢?可能夠了吧!我們常見(jiàn)的漢字就是用兩個(gè)字節(jié)表示的,如GB2312。
2.2.3 GB*編碼
為了顯示中文,我們必須設(shè)計(jì)一套編碼規(guī)則用于將漢字轉(zhuǎn)換為計(jì)算機(jī)可以接受的數(shù)字系統(tǒng)的數(shù)。顯示中文的常用字符編碼有:GB2312、GBK、GB18030
2.2.3.1 GB2312
GB2312,用于漢字處理、漢字通信等系統(tǒng)之間的信息交換,通行于中國(guó)大陸。它的編碼規(guī)則是:小于127的字符的意義與原來(lái)相同,但兩個(gè)大于127的字符連在一起時(shí),就表示一個(gè)漢字,前面的一個(gè)字節(jié)(他稱之為高字節(jié))從0xA1用到 0xF7,后面一個(gè)字節(jié)(低字節(jié))從0xA1到0xFE,這樣我們就可以組合出大約7000多個(gè)簡(jiǎn)體漢字了。在這些編碼里,還把數(shù)學(xué)符號(hào)、羅馬希臘的 字母、日文的假名們都編進(jìn)去了,連在ASCII里本來(lái)就有的數(shù)字、標(biāo)點(diǎn)、字母都統(tǒng)統(tǒng)重新編了兩個(gè)字節(jié)長(zhǎng)的編碼,這就是常說(shuō)的全角字符,而原來(lái)在127 號(hào)以下的那些就叫半角字符了
雖然GB2312收錄了這么多漢子,他所覆蓋的使用率可以達(dá)到99%,但是對(duì)于那些不常見(jiàn)的漢字,例如人名、地名、古漢語(yǔ),它就不能處理了,于是就有下面的GBK、GB 18030的出現(xiàn)。
2.2.3.2 GBK
GBK,全稱《漢字內(nèi)碼擴(kuò)展規(guī)范》,由中華人民共和國(guó)全國(guó)信息技術(shù)標(biāo)準(zhǔn)化技術(shù)委員會(huì)1995年12月1日制訂,也是漢字編碼的標(biāo)準(zhǔn)之一。
GBK是GB2312的擴(kuò)展,他向下與GB2312兼容,,向上支持ISO 10646.1國(guó)際標(biāo)準(zhǔn),是前者向后者過(guò)渡過(guò)程中的一個(gè)承上啟下的標(biāo)準(zhǔn)。同時(shí)它是使用雙字節(jié)編碼方案,其編碼范圍從8140至FEFE(剔除xx7F),首字節(jié)在 81-FE 之間,尾字節(jié)在 40-FE 之間,共23940個(gè)碼位,共收錄了21003個(gè)漢字。
2.2.3.3 GB18030
GB18030,全稱:國(guó)家標(biāo)準(zhǔn)GB 18030-2005《信息技術(shù) 中文編碼字符集》,是我國(guó)計(jì)算機(jī)系統(tǒng)必須遵循的基礎(chǔ)性標(biāo)準(zhǔn)之一,GB18030有兩個(gè)版本:GB18030-2000和GB18030-2005。
GB18030-2000是GBK的取代版本,僅規(guī)定了常用非漢字符號(hào)和27533個(gè)漢字(包括部首、部件等)的編碼,它的主要特點(diǎn)是在GBK基礎(chǔ)上增加了CJK統(tǒng)一漢字?jǐn)U充A的漢字。
而GB18030-2005是全文強(qiáng)制性標(biāo)準(zhǔn),市場(chǎng)上銷售的產(chǎn)品必須符合,它是GB18030-2000的基礎(chǔ)上增加了42711個(gè)漢字和多種我國(guó)少數(shù)民族文字的編碼。
GB18030標(biāo)準(zhǔn)采用單字節(jié)、雙字節(jié)和四字節(jié)三種方式對(duì)字符編碼
與UTF-8相同,采用多字節(jié)編碼,每個(gè)字可以由1個(gè)、2個(gè)或4個(gè)字節(jié)組成。
編碼空間龐大,最多可定義161萬(wàn)個(gè)字符。
支持中國(guó)國(guó)內(nèi)少數(shù)民族的文字,不需要?jiǎng)佑迷熳謪^(qū)。
漢字收錄范圍包含繁體漢字以及日韓漢字
2.2.4 Unicode編碼
正如前面前面所提到的一樣,世界存在這么多國(guó)家,也存在著多種編碼風(fēng)格,像中文的GB232、GBK、GB18030,這樣亂搞一套,雖然在本地運(yùn)行沒(méi)有問(wèn)題,但是一旦出現(xiàn)在網(wǎng)絡(luò)上,由于互不兼容,訪問(wèn)則會(huì)出現(xiàn)亂碼。為了解決這個(gè)問(wèn)題,偉大的Unicode編碼騰空出世。
2.2.4.1 Unicode
Unicode又稱為統(tǒng)一碼、萬(wàn)國(guó)碼、單一碼,它是為了解決傳統(tǒng)的字符編碼方案的局限而產(chǎn)生的,它為每種語(yǔ)言中的每個(gè)字符設(shè)定了統(tǒng)一并且唯一的二進(jìn)制編碼,以滿足跨語(yǔ)言、跨平臺(tái)進(jìn)行文本轉(zhuǎn)換、處理的要求。可以想象Unicode作為一個(gè)字符大容器,它將世界上所有的符號(hào)都包含其中,并且每一個(gè)符號(hào)都有自己獨(dú)一無(wú)二的編碼,這樣就從根本上解決了亂碼的問(wèn)題。所以Unicode是一種所有符號(hào)的編碼。
Unicode伴隨著通用字符集的標(biāo)準(zhǔn)而發(fā)展,同時(shí)也以書本的形式對(duì)外發(fā)表,它是業(yè)界的標(biāo)準(zhǔn),對(duì)世界上大部分的文字系統(tǒng)進(jìn)行了整理、編碼,使得電腦可以用更為簡(jiǎn)單的方式來(lái)呈現(xiàn)和處理文字。Unicode至今仍在不斷增修,迄今而至已收入超過(guò)十萬(wàn)個(gè)字符,它備受業(yè)界認(rèn)可,并廣泛地應(yīng)用于電腦軟件的國(guó)際化與本地化過(guò)程。
我們知道Unicode是為了解決傳統(tǒng)的字符編碼方案的局限而產(chǎn)生的,對(duì)于傳統(tǒng)的編碼方式而言,他們都存在一個(gè)共同的問(wèn)題:無(wú)法支持多語(yǔ)言環(huán)境,這對(duì)于互聯(lián)網(wǎng)這個(gè)開(kāi)放的環(huán)境是不允許的。而目前幾乎所有的電腦系統(tǒng)都支持基本拉丁字母,并各自支持不同的其他編碼方式。Unicode為了和它們相互兼容,其首256字符保留給ISO 8859-1所定義的字符,使既有的西歐語(yǔ)系文字的轉(zhuǎn)換不需特別考量;并且把大量相同的字符重復(fù)編到不同的字符碼中去,使得舊有紛雜的編碼方式得以和Unicode編碼間互相直接轉(zhuǎn)換,而不會(huì)丟失任何信息
一個(gè)字符的Unicode編碼是確定的,但是在實(shí)際傳輸過(guò)程中,由于不同系統(tǒng)平臺(tái)的設(shè)計(jì)不一定一致,以及出于節(jié)省空間的目的,對(duì)Unicode編碼的實(shí)現(xiàn)方式有所不同。Unicode的實(shí)現(xiàn)方式稱為Unicode轉(zhuǎn)換格式(Unicode Transformation Format,簡(jiǎn)稱為UTF)。
Unicode編碼是現(xiàn)在大部分計(jì)算機(jī)內(nèi)部使用的編碼格式
Unicode是字符集,它主要有UTF-8、UTF-16、UTF-32三種實(shí)現(xiàn)方式。由于UTF-8是目前主流的實(shí)現(xiàn)方式,UTF-16、UTF-32相對(duì)而言使用較少,所以下面就主要介紹UTF-8
2.2.4.2 UCS
提到Unicode可能有必要了解下,UCS。UCS(Universal Character Set,通用字符集),是由ISO制定的ISO 10646(或稱ISO/IEC 10646)標(biāo)準(zhǔn)所定義的標(biāo)準(zhǔn)字符集。它包括了其他所有字符集,保證了與其他字符集的雙向兼容,即,如果你將任何文本字符串翻譯到UCS格式,然后再翻譯回原編碼,你不會(huì)丟失任何信息。
UCS不僅給每個(gè)字符分配一個(gè)代碼,而且賦予了一個(gè)正式的名字。表示一個(gè)UCS或Unicode值的十六進(jìn)制數(shù)通常在前面加上U+,例如U+0041代表字符A。
2.2.4.3 Little endian & Big endian
由于各個(gè)系統(tǒng)平臺(tái)的設(shè)計(jì)不同,可能會(huì)導(dǎo)致某些平臺(tái)對(duì)字符的理解不同(比如字節(jié)順序的理解)。這時(shí)將會(huì)導(dǎo)致同意字節(jié)流可能會(huì)被解釋為不同的內(nèi)容。如某個(gè)字符的十六進(jìn)制為4E59,拆分為4E、59,在MAC上讀取時(shí)是歐諾個(gè)低位開(kāi)始的,那么MAC在遇到該字節(jié)流時(shí)會(huì)被解析為594E,找到的字符為奎,但是在Windows平臺(tái)是從高字節(jié)開(kāi)始讀取,為4E59,找到的字符為乙。也就是說(shuō)在Windows平臺(tái)保存的“乙”跑到MAC平臺(tái)上就變成了“奎”。這樣勢(shì)必會(huì)引起混亂,于是在Unicode編碼中采用了大頭(Big endian)、小頭(Little endian)兩種方式來(lái)進(jìn)行區(qū)分。即第一個(gè)字節(jié)在前,就是大頭方式,第二個(gè)字節(jié)在前就是小頭方式。那么這個(gè)時(shí)候就出現(xiàn)了一個(gè)問(wèn)題:計(jì)算機(jī)怎么知道某個(gè)文件到底是采用哪種編碼方式的呢?
Unicode規(guī)范中定義,每一個(gè)文件的最前面分別加入一個(gè)表示編碼順序的字符,這個(gè)字符的名字叫做 零寬度非換行空格(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。這正好是兩個(gè)字節(jié),而且FF比FE大1。
如果一個(gè)文本文件的頭兩個(gè)字節(jié)是FE FF,就表示該文件采用大頭方式;如果頭兩個(gè)字節(jié)是FF FE,就表示該文件采用小頭方式。
2.2.4.4 BOM
既然底層存儲(chǔ)分為了大端和小端兩種模式,那么假如我們現(xiàn)在有一個(gè)文件,計(jì)算機(jī)又是怎么知道當(dāng)前是采用的大端模式還是小端模式呢?
BOM 即 byte order mark(字節(jié)順序標(biāo)記),出現(xiàn)在文本文件頭部。BOM 就是用來(lái)標(biāo)記當(dāng)前文件采用的是大端模式還是小端模式存儲(chǔ)。我想這個(gè)大家應(yīng)該都見(jiàn)過(guò),平常在使用記事本保存文檔的時(shí)候,需要選擇采用的是大端還是小端:
在 UCS 編碼中有一個(gè)叫做 Zero Width No-Break Space(零寬無(wú)間斷間隔)的字符,對(duì)應(yīng)的編碼是 FEFF。FEFF 是不存在的字符,正常來(lái)說(shuō)不應(yīng)該出現(xiàn)在實(shí)際數(shù)據(jù)傳輸中。
但是為了區(qū)分大端模式和小端模式,UCS 規(guī)范建議在傳輸字節(jié)流前,先傳輸字符 Zero Width No-Break Space。而且根據(jù)這個(gè)字符的順序來(lái)區(qū)分大端模式和小端模式。
下表就是不同編碼的 BOM:
有了這個(gè)規(guī)范,解析文件的時(shí)候就可以知道當(dāng)前編碼以及其存儲(chǔ)模式了。注意這里 UTF-8 編碼比較特殊,因?yàn)楸旧?UTF-8 編碼有特殊的順序格式規(guī)定,所以 UTF-8 本身并沒(méi)有什么大端模式和小端模式的區(qū)別.
根據(jù) UTF-8 本身的特殊編碼格式,在沒(méi)有 BOM 的情況下也能被推斷出來(lái),但是因?yàn)槲④浭墙ㄗh都加上 BOM,所以目前存在了帶 BOM 的 UTF-8 文件和不帶 BOM 的 UTF-8 文件,這兩種格式在某些場(chǎng)景可能會(huì)出現(xiàn)不兼容的問(wèn)題,所以在平常使用中也可以稍微留意這個(gè)問(wèn)題
2.2.4.5 UTF-8
UTF-8是一種針對(duì)Unicode的可變長(zhǎng)度字符編碼,可以使用1~4個(gè)字節(jié)表示一個(gè)符號(hào),根據(jù)不同的符號(hào)而變化字節(jié)長(zhǎng)度。它可以用來(lái)表示Unicode標(biāo)準(zhǔn)中的任何字符,且其編碼中的第一個(gè)字節(jié)仍與ASCII兼容,這使得原來(lái)處理ASCII字符的系統(tǒng)無(wú)須或只須做少部份修改,即可繼續(xù)使用。因此,它逐漸成為電子郵件、網(wǎng)頁(yè)及其他存儲(chǔ)或傳送文字的應(yīng)用中,優(yōu)先采用的編碼。
UTF-8使用一到四個(gè)字節(jié)為每個(gè)字符編碼,編碼規(guī)則如下:
- 對(duì)于單字節(jié)的符號(hào),字節(jié)的第一位設(shè)為0,后面7位為這個(gè)符號(hào)的unicode碼。因此對(duì)于英語(yǔ)字母,UTF-8編碼和ASCII碼是相同的。
- 對(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碼
| 0000 ~007F | 0XXX XXXX |
| 0080 ~07FF | 110X XXXX 10XX XXXX |
| 0800 ~FFFF | 1110XXXX 10XX XXXX 10XX XXXX |
| 1 0000 ~1F FFFF | 1111 0XXX 10XX XXXX 10XX XXXX 10XX XXXX |
| 20 0000 ~3FF FFFF | 1111 10XX 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX |
| 400 0000 ~7FFF FFFF | 1111 110X 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX |
根據(jù)上面的轉(zhuǎn)換表,理解UTF-8的轉(zhuǎn)換編碼規(guī)則就變得非常簡(jiǎn)單了:第一個(gè)字節(jié)的第一位如果為0,則表示這個(gè)字節(jié)單獨(dú)就是一個(gè)字符;如果為1,連續(xù)多少個(gè)1就表示該字符占有多少個(gè)字節(jié)。
以漢字嚴(yán)為例,演示如何實(shí)現(xiàn)UTF-8編碼。
已知嚴(yán)的unicode是4E25(100 1110 0010 0101),根據(jù)上表,可以發(fā)現(xiàn)4E25處在第三行的范圍內(nèi)(0000 0800-0000 FFFF),因此嚴(yán)的UTF-8編碼需要三個(gè)字節(jié),即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,從嚴(yán)的最后一個(gè)二進(jìn)制位開(kāi)始,依次從后向前填入格式中的x,多出的位補(bǔ)0。這樣就得到了,嚴(yán)的UTF-8編碼是11100100 10111000 10100101,轉(zhuǎn)換成十六進(jìn)制就是E4B8A5
2.2.4.6 Unicode與UTF-8之間的轉(zhuǎn)換
通過(guò)上面的例子我們可以看到”嚴(yán)”的Unicode碼為4E25,UTF-8編碼為E4B8A5,他們兩者是不一樣的,需要通過(guò)程序的轉(zhuǎn)換來(lái)實(shí)現(xiàn),在Window平臺(tái)最簡(jiǎn)單的直觀的方法就是記事本
在最下面的編碼(E)處有四個(gè)選項(xiàng):ANSI、Unicode、Unicode big endian、UTF-8。
- ANSI:記事本的默認(rèn)的編碼方式,對(duì)于英文文件是ASCII編碼,對(duì)于簡(jiǎn)體中文文件是GB2312編碼。注意:不同 ANSI 編碼之間互不兼容,當(dāng)信息在國(guó)際間交流時(shí),無(wú)法將屬于兩種語(yǔ)言的文字,存儲(chǔ)在同一段ANSI編碼的文本中
- Unicode:UCS-2編碼方式,即直接用兩個(gè)字節(jié)存入字符的Unicode碼。該方式是小頭little endian方式。
- Unicode big endian:UCS-2編碼方式,大頭方式。
- UTF-8:閱讀上面(UTF-8)。
實(shí)例:在記事本中輸入”嚴(yán)”字,依次選擇ANSI、Unicode、Unicode big endian、UTF-8四種編碼風(fēng)格,然后另存為,使用EditPlus文本工具使用”16進(jìn)制查看器”進(jìn)行查看,得到如下結(jié)果:
- ANSI:兩個(gè)字節(jié)D1 CF正是”嚴(yán)”的GB2312編碼。
- Unicode:四個(gè)字節(jié)”FF FE 25 4E”,其中FF FE表示小頭存儲(chǔ)方式,真正的編碼為”25 4E”。
- Unicode big endian:四個(gè)字節(jié)”FE FF 4E 25″,”FE FF”表示大頭存儲(chǔ)方式,真正編碼為”4E 25″。
- UTF-8:編碼是六個(gè)字節(jié)EF BB BF E4 B8 A5,前三個(gè)字節(jié)EF BB BF表示這是UTF-8編碼,后三個(gè)E4B8A5就是嚴(yán)的具體編碼,它的存儲(chǔ)順序與編碼順序是一致的。
點(diǎn)擊了解源碼反碼補(bǔ)碼和漢字與十六進(jìn)制互轉(zhuǎn)
參考文獻(xiàn)地址:
Unicode維基百科:http://zh.wikipedia.org/wiki/Unicode
Unicode百度百科:http://baike.baidu.com/view/40801.html
字符編碼筆記:ASCII,Unicode和UTF-8:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
UTF-8百度百科:http://baike.baidu.com/view/25412.html
編碼:http://baike.baidu.com/subview/237708/11062012.html(百度百科)
字符:http://baike.baidu.com/view/263416.html(百度百科)
字符集:http://baike.baidu.com/view/51987.html(百度百科)
字符編碼:http://baike.baidu.com/view/1204863.html(百度百科)
字符集和字符編碼:http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html(吳秦)
ASCII:http://baike.baidu.com/view/15482.html
GB2312:http://baike.baidu.com/view/443268.html
GBK:http://baike.baidu.com/view/931619.html
GB18030:http://baike.baidu.com/view/889058.html
總結(jié)
以上是生活随笔為你收集整理的Java中文乱码详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: mongoose 通过账号密码连接 Er
- 下一篇: json-server模拟后台接口