日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java中文乱码详解

發(fā)布時(shí)間:2023/12/10 java 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java中文乱码详解 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • 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中文亂碼問題
      • 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 問題起源
      • 2.1.2 常見字符編碼
    • 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。為了安全起見,建議始終帶參數(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ō)我們常見的.class文件中的內(nèi)容無(wú)論是中文字符還是英文字符,他們都已經(jīng)轉(zhuǎn)換為Unicode編碼格式了。
在這一步中對(duì)JSP源文件的處理方式有點(diǎn)兒不同:WEB容器調(diào)用JSP編譯器,JSP編譯器首先會(huì)查看JSP文件是否設(shè)置了文件編碼格式,如果沒有設(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è)置都沒有關(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è)定的編碼格式,如果沒有設(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ì)有什么問題,javaWeb場(chǎng)景過(guò)多需要了解URL、get、POST的編碼,servlet的解碼

    1.6.2 I/O操作

    所謂亂碼問題無(wú)非就是轉(zhuǎn)碼過(guò)程中編碼格式的不統(tǒng)一產(chǎn)生的,比如編碼時(shí)采用UTF-8,解碼采用GBK,但最根本的原因是字符到字節(jié)或者字節(jié)到字符的轉(zhuǎ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ì)涉及到編碼的問題,如果兩者之間的編碼格式不同可能會(huì)出現(xiàn)問題。例如存在一個(gè)問題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é)讀取文件和按字符讀取沒什么區(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)解析為字符,如果沒有指定編碼格式則采用系統(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)編碼格式,如果沒有指定編碼格式則默認(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()方法:

    public String(byte bytes[], int offset, int length, Charset charset) {if (charset == null)throw new NullPointerException("charset");checkBounds(bytes, offset, length);this.value = StringCoding.decode(charset, bytes, offset, length);}

    decode方法和encode對(duì)編碼格式的處理是一樣的。
    對(duì)于以上兩種情況我們只需要設(shè)置統(tǒng)一的編碼格式一般都不會(huì)產(chǎ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編碼

    public class Test02 {public static void main(String[] args) throws UnsupportedEncodingException {String string = "我是 cm";Test02.printChart(string.toCharArray());Test02.printChart(string.getBytes("ISO-8859-1"));Test02.printChart(string.getBytes("GBK"));Test02.printChart(string.getBytes("UTF-8"));}/*** char轉(zhuǎn)換為16進(jìn)制*/public static void printChart(char[] chars){for(int i = 0 ; i < chars.length ; i++){System.out.print(Integer.toHexString(chars[i]) + " "); }System.out.println("");}/*** byte轉(zhuǎn)換為16進(jìn)制*/public static void printChart(byte[] bytes){for(int i = 0 ; i < bytes.length ; i++){String hex = Integer.toHexString(bytes[i] & 0xFF); if (hex.length() == 1) { hex = '0' + hex; } System.out.print(hex.toUpperCase() + " "); }System.out.println("");} } -------------------------outPut: 6211 662f 20 63 6d 3F 3F 20 63 6D CE D2 CA C7 20 63 6D E6 88 91 E6 98 AF 20 63 6D

    通過(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方式直接訪問。
    • 頁(yè)面鏈接。
    • 表單get提交
    • 表單post提交

    1.7.2 URL方式

    對(duì)于URL,如果該URL中全部都是英文的那倒是沒有什么問題,如果有中文就要涉及到編碼了。如何編碼?根據(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ì)我是的編碼情況如下:

    path部分Query String
    FirefoxE6 88 91 E6 98 AFE6 88 91 E6 98 AF
    ChromeE6 88 91 E6 98 AFE6 88 91 E6 98 AF
    IEE6 88 91 E6 98 AFCE 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 方法中完成的:

    protected void convertURI(MessageBytes uri, Request request) throws Exception { ByteChunk bc = uri.getByteChunk(); int length = bc.getLength(); CharChunk cc = uri.getCharChunk(); cc.allocate(length, -1); String enc = connector.getURIEncoding(); //獲取URI解碼集if (enc != null) { B2CConverter conv = request.getURIConverter(); try { if (conv == null) { conv = new B2CConverter(enc); request.setURIConverter(conv); } } catch (IOException e) {...} if (conv != null) { try { conv.convert(bc, cc, cc.getBuffer().length - cc.getEnd()); uri.setChars(cc.getBuffer(), cc.getStart(), cc.getLength()); return; } catch (IOException e) {...} } } // Default encoding: fast conversion byte[] bbuf = bc.getBuffer(); char[] cbuf = cc.getBuffer(); int start = bc.getStart(); for (int i = 0; i < length; i++) { cbuf[i] = (char) (bbuf[i + start] & 0xff); } uri.setChars(cbuf, 0, length); }

    從上面的代碼可知,對(duì)URI的解碼操作是首先獲取Connector的解碼集,該配置在server.xml中

    <Connector URIEncoding="utf-8" />

    如果沒有定義則會(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方法的一部分:

    //獲取編碼String enc = getCharacterEncoding();//獲取ContentType 中定義的 Charsetboolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();if (enc != null) { //如果設(shè)置編碼不為空,則設(shè)置編碼為enc parameters.setEncoding(enc);if (useBodyEncodingForURI) { //如果設(shè)置了Chartset,則設(shè)置queryString的解碼為ChartSet parameters.setQueryStringEncoding(enc); }} else { //設(shè)置默認(rèn)解碼方式 parameters.setEncoding(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);if (useBodyEncodingForURI) {parameters.setQueryStringEncoding(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);}}

    從上面代碼可以看出對(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)生亂碼問題的,所以我們更加傾向于通過(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)行解碼,如果沒有設(shè)置則會(huì)使用默認(rèn)的ISO-8859-1來(lái)解碼。假如我們?cè)陧?yè)面將編碼設(shè)置為UTF-8,而URIEncoding設(shè)置的不是或者沒有設(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)亂碼問題。當(dāng)然這個(gè)字符集編碼我們是可以自己設(shè)定的:request.setCharacterEncoding(charset)

    1.8 解決URL中文亂碼問題

    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解決該問題有一次轉(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ù):我是cm

    1.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ù):我是cm

    2 計(jì)算機(jī)編碼歷史

    2.1 認(rèn)識(shí)字符集

    2.1.1 問題起源

    對(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亂碼的問題就在于JVM與對(duì)應(yīng)的操作系統(tǒng)/瀏覽器進(jìn)行編碼格式轉(zhuǎn)換時(shí)出現(xiàn)了錯(cuò)誤。

    其實(shí)解決 JAVA 程序中的漢字編碼問題的方法往往很簡(jiǎn)單,但理解其背后的原因,定位問題,還需要了解現(xiàn)有的漢字編碼和編碼轉(zhuǎn)換。

    2.1.2 常見字符編碼

    計(jì)算機(jī)要準(zhǔn)確的處理各種字符集文字,需要進(jìn)行字符編碼,以便計(jì)算機(jī)能夠識(shí)別和存儲(chǔ)各種文字。常見的字符編碼主要包括: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)的是視覺上的刺激。
    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ǔ)各種文字。
    常見字符集名稱: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)生的問題也就來(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é)呢?可能夠了吧!我們常見的漢字就是用兩個(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ì)于那些不常見的漢字,例如人名、地名、古漢語(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)行沒有問題,但是一旦出現(xiàn)在網(wǎng)絡(luò)上,由于互不兼容,訪問則會(huì)出現(xiàn)亂碼。為了解決這個(gè)問題,偉大的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ú)二的編碼,這樣就從根本上解決了亂碼的問題。所以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ú)法支持多語(yǔ)言環(huán)境,這對(duì)于互聯(lián)網(wǎng)這個(gè)開放的環(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è)低位開始的,那么MAC在遇到該字節(jié)流時(shí)會(huì)被解析為594E,找到的字符為奎,但是在Windows平臺(tái)是從高字節(jié)開始讀取,為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è)問題:計(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)該都見過(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 本身并沒有什么大端模式和小端模式的區(qū)別.

    根據(jù) UTF-8 本身的特殊編碼格式,在沒有 BOM 的情況下也能被推斷出來(lái),但是因?yàn)槲④浭墙ㄗh都加上 BOM,所以目前存在了帶 BOM 的 UTF-8 文件和不帶 BOM 的 UTF-8 文件,這兩種格式在某些場(chǎng)景可能會(huì)出現(xiàn)不兼容的問題,所以在平常使用中也可以稍微留意這個(gè)問題

    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。剩下的沒有提及的二進(jìn)制位,全部為這個(gè)符號(hào)的unicode碼
    UnicodeUTF-8
    0000 ~007F0XXX XXXX
    0080 ~07FF110X XXXX 10XX XXXX
    0800 ~FFFF1110XXXX 10XX XXXX 10XX XXXX
    1 0000 ~1F FFFF1111 0XXX 10XX XXXX 10XX XXXX 10XX XXXX
    20 0000 ~3FF FFFF1111 10XX 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX
    400 0000 ~7FFF FFFF1111 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)制位開始,依次從后向前填入格式中的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ǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。