<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    持之以恒

    記錄本
    posts - 4, comments - 32, trackbacks - 0, articles - 74
      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

    java中字符集的問題

    Posted on 2012-09-19 11:33 小白19870626 閱讀(21389) 評論(4)  編輯  收藏 所屬分類: java

    1、JVM中單個字符占用的字節(jié)長度跟編碼方式有關,而默認編碼方式又跟平臺是一一對應的或說平臺決定了默認字符編碼方式;

    2、對于單個字符:ISO-8859-1單字節(jié)編碼,GBK雙字節(jié)編碼,UTF-8三字節(jié)編碼因此中文平臺(中文平臺默認字符集編碼GBK)下一個中文字符占2個字節(jié),而英文平臺(英文平臺默認字符集編碼Cp1252(類似于ISO-8859-1))。

    3、getBytes()、getBytes(encoding)函數(shù)的作用是使用系統(tǒng)默認或者指定的字符集編碼方式,將字符串編碼成字節(jié)數(shù)組

    編碼方式?jīng)Q定字節(jié)長度;在中文平臺下,默認的字符集編碼是GBK此時如果使用getBytes()或getBytes("GBK"),則按照GBK的編碼規(guī)則將每個中文字符用2個byte表示所以我們看到"中文"最終GBK編碼結(jié)果就是: -42 -48 -50 -60 。-42和-48代表了"中"字,而"-50"和"-60"則代表了"文"字。

    在中文平臺下,如果指定的字符集編碼是UTF-8,那么按照UTF-8對中文的編碼規(guī)則:每個中文用3個字節(jié)表示,那么"中文"這兩個字符最終被編碼成:-28 -72 -83、-26 -106 -121兩組。每3個字節(jié)代表一個中文字符。

    在中文平臺下,如果指定的字符集編碼是ISO-8859-1,由于此字符集是單字節(jié)編碼,所以使用getBytes("ISO-8859-1")時,每個字符只取一個字節(jié),每個漢字只取到了一半的字符。另外一半的字節(jié)丟失了。由于這一半的字符在字符集中找不到對應的字符,所以默認使用編碼63代替,也就是?。

    在英文平臺下,默認的字符集編碼是Cp1252(類似于ISO-8859-1),如果使用GBK、UTF-8進行編碼,得到的字節(jié)數(shù)組依然是正確的(GBK4個字節(jié),UTF-8是6個字節(jié))。因為在JVM內(nèi)部是以Unicode存儲字符串的,使用getBytes(encoding)會讓JVM進行一次Unicode到指定編碼之間的轉(zhuǎn)換。對于GBK,JVM依然會轉(zhuǎn)換成4個字節(jié),對于UTF-8,JVM依然會轉(zhuǎn)換成6個字節(jié)。但是對于ISO-8859-1,則由于無法轉(zhuǎn)換(2個字節(jié)--->1個字節(jié),截取了一半的字節(jié)),所以轉(zhuǎn)換后的結(jié)果是錯誤的。

    在中文平臺下,默認的字符集編碼是GBK,于是content.getBytes()得到的是什么呢?就是下面這4個字節(jié):

    byte[0] = -42 hex string = ffffffd6

    byte[1] = -48 hex string = ffffffd0

    byte[2] = -50 hex string = ffffffce

    byte[3] = -60 hex string = ffffffc4

    如果新的encoding是GBK,那么經(jīng)過解碼后,由于一個字符用2個字節(jié)表示。于是最終的結(jié)果就是:

    char[0]='中' --- byte[0] + byte[1]

    char[1]='文' --- byte[2] + byte[3]

    如果新的encoding是ISO-8859-1,那么經(jīng)過解碼后,由于一個字符用1個字節(jié)表示,于是原來本應該2個字節(jié)一起解析的變成單個字節(jié)解析,每個字節(jié)都代表了一個漢字字符的一半。這一半的字節(jié)在ISO-8859-1中找不到對應的字符,就變成了"?"了,最終的結(jié)果:

    char[0]='?' ---- byte[0]

    char[1]='?' ---- byte[1]

    char[2]='?' ---- byte[2]

    char[3]='?' ---- byte[3]

    如果新的encoding是UTF-8,那么經(jīng)過解碼后,由于一個字符用3個字節(jié)表示,于是原來4個字節(jié)的數(shù)據(jù)無法正常的解析成UTF-8的數(shù)據(jù),最終的結(jié)果也是每一個都變成"?"。

    char[0]='?' ---- byte[0]

    char[1]='?' ---- byte[1]

    char[2]='?' ---- byte[2]

    char[3]='?' ---- byte[3]

    如果是在英文平臺下,由于默認的編碼方式是Cp1252,于是content.getBytes()得到的字節(jié)都是被截去一半的殘留字符,所以我們看到在英文平臺下,不論指定的encoding是GBK、UTF-8,其結(jié)果和ISO-8859-1都是一樣的。

    記住:這個方法再次證明了String的getBytes()方法的危險性,如果我們使用new String(str.getBytes(), encoding)對字符串進行重新編碼解碼時,我們一定要清楚str.getBytes()方法返回的字節(jié)數(shù)組的長度、內(nèi)容到底是什么,因為在接下來使用新的encoding進行編碼解碼時,Java并不會自動地對字節(jié)數(shù)組進行擴展以適應新的encoding。而是按照新的編碼方法直接對該字節(jié)數(shù)組進行解析。于是結(jié)果就像上面的例子一樣,同樣是4個原始字節(jié),有些每2個一組進行解析,有些每個一組進行解析,有些每3個一組進行解析。其結(jié)果就只能看那種編碼方式合適了。


    結(jié)論:相同的平臺下,同一個中文字符,在不同的編碼方式下,得到的是完全不同的字節(jié)數(shù)組。這些字節(jié)數(shù)組有可能是正確的(只要該字符集支持中文),也可能是完全錯誤的(該字符集不支持中文)。

    記住:不要輕易地使用或濫用String類的getBytes(encoding)方法,更要盡量避免使用getBytes()方法。因為這個方法是平臺依賴的,在平臺不可預知的情況下完全可能得到不同的結(jié)果如果一定要進行字節(jié)編碼,則用戶要確保encoding的方法就是當初字符串輸入時的encoding。

    ———————————————————————————————————————————————————

     

    和getBytes(encoding)不同,toCharArray()返回的是"自然字符"。但是這個"自然字符"的數(shù)目和內(nèi)容卻是由原始的編碼方式?jīng)Q定的

    FileWriter是字符流輸出流,而OutputStreamWriter是字節(jié)流輸出流在中文平臺下,如果使用FileWriter,不論你如何設置字符集都不會起作用。因為它采用的是默認的系統(tǒng)字符集。即便你設置了System.setProperty("file.encoding", "ISO-8859-1"),或者在運行時給予參數(shù)-Dfile.encoding=UTF-8都不會起作用。你會發(fā)現(xiàn)它最終還是都已"GB2312"或者"GBK"的方式保存。

    在中文平臺下,如果使用OutputStreamWriter,則在后臺寫入時會把字符流轉(zhuǎn)換成字節(jié)流,此時指定的編碼字符集就起作用了。可以看到在指定GBK、UTF-8的情況下中文可以正常的保存和讀取,同時文件按照我們給定的方式保存了。而對于ISO-8859-1則變成了?,這再次證明了采用ISO-8859-1是不能保存中文的,而且會因為中文編碼在ISO-8859-1的編碼中找不到對應的字符而默認轉(zhuǎn)換成?。

    在英文平臺下,如果使用FileWriter,不論你如何設置字符集同樣都不會起作用。所有的文件都將按照ISO-8859-1的編碼方式保存,毫無疑問地變成了?。在英文平臺下,如果使用OutputStreamWriter,則只有當我們把字符和文件的編碼方式正確設置為GBK、UTF-8的情況下,中文才能正確的保存并顯示。

    通過上述的實驗證明,為了確保在不同的平臺下,客戶端輸入的中文可以被正確地解析、保存、讀取。最好的辦法就是使用OutputStreamWriter配合UTF-8編碼。如果不想使用UTF-8編碼,那么可以考慮使用GB2312,不建議使用GBK、GB18030。因為對于某些老式的文本編輯器,甚至不支持GBK、GB18030的編碼,但是對于GB2312則是一定支持的。因為前兩者都不是國標但后者是。

    關于String的getBytes(),getBytes(encoding)和new String(bytes, encoding)這三個方法,非常值得注意:A.getBytes():使用平臺默認的編碼方式(通過file.encoding屬性獲取)方式來將字符串轉(zhuǎn)換成byte[]。得到的是字符串最原始的字節(jié)編碼值。

    B.getBytes(NAME_OF_CHARSET):使用指定的編碼方式將字符串轉(zhuǎn)換成byte[],如果想要得到正確的字節(jié)數(shù)組,程序員必須給出正確的NAME_OF_CHARSET。否則得到的就不會得到正確的結(jié)果。

    C.new String(bytes, encoding):如果我們在客戶端使用UTF-8編碼的JSP頁面發(fā)出請求,瀏覽器編碼后的UTF-8字節(jié)會以ISO-8859-1的形式傳遞到服務器端。所以要得到經(jīng)HTTP協(xié)議傳輸?shù)脑甲止?jié),我們需要先調(diào)用getBytes("ISO-8859-1")得到原始的字節(jié),但由于我們客戶端的原始編碼是UTF-8,如果繼續(xù)按照ISO-8859-1解碼,那么得到的將不是一個中文字符,而是3個亂碼的字符。所以我們需要再次調(diào)用new String(bytes,"UTF-8"),將字節(jié)數(shù)組按照UTF-8的格式,每3個一組進行解碼,才能還原為客戶端的原始字符。

    D.String的getBytes()、getBytes(NAME_OF_CHARSET)方法都是比較微妙的方法,原則上:傳輸時采用的是什么編碼,我們就需要按照這種編碼得到字節(jié)。new String(bytes, NAME_OF_CHARSET)則更加需要小心,原則上:客戶端采用的是什么編碼,那么這里的NAME_OF_CHARSET就必須和客戶端保持一致。例如JSP頁面是GBK,那么我們接收頁面?zhèn)鬟f而來的參數(shù)時就必須使用new String(parameter.getBytes("ISO-8859-1"), "GBK");如果使用了錯誤的解碼方式,如使用了UTF-8,那么得到的很有可能就是亂碼了。也就是說:GBK--->ISO-8859-1--->GBK、UTF-8--->ISO-8859-1--->UTF-8的轉(zhuǎn)換過程是沒有問題的。但是GBK--->ISO-8859-1--->UTF-8、UTF-8--->ISO-8859-1--->GBK的字節(jié)直接轉(zhuǎn)碼則可能導致亂碼,需要另外的轉(zhuǎn)換過程。

    記住:謹慎地使用getBytes(NAME_OF_CHARSET)和new String(bytes, NAME_OF_CHARSET),除非你很清楚的知道原始的字符編碼和傳輸協(xié)議使用的編碼。推薦使用基于服務器的配置、過濾器設置request/response的characterEncoding、content type屬性。還有就是JSP頁面的pageEncoding屬性、HTML meta元素的content type屬性。盡量避免頻繁的在代碼中進行字符串轉(zhuǎn)碼,即降低了效率又增加了風險。

    問題:我們在web應用開發(fā)過程中經(jīng)常遇到輸出某種編碼的字符,如iso8859-1等,如何輸出一個某種編碼的字符串?

    代碼如下:
    1. public class StringTest {
    2. public static void main(String[] args){
    3. String str="我是中國人";
    4. try {
    5. byte[] by=str.getBytes("ISO-8859-1");
    6. byte[] by1=str.getBytes("GBK");
    7. byte[] by2=str.getBytes("utf-8");
    8. System.out.println("ISO-8859-1");
    9. System.out.println(by.length);
    10. System.out.println(by[0]);
    11. System.out.println("GBK");
    12. System.out.println(by1.length);
    13. System.out.println(by1[0]);
    14. System.out.println("utf-8");
    15. System.out.println(by2.length);
    16. System.out.println(by2[0]);
    17. //先將字符串以ISO-8859-1進行編碼
    18. String tempStr = new String(str.getBytes(), "ISO-8859-1");
    19. //由于中文平臺一個中文占兩上字節(jié),所以以ISO-8859-1進行編碼時,輸出為10個字符的亂碼
    20. System.out.println(tempStr);
    21. //再獲取該字符串的ISO-8859-1字節(jié),以GBK解碼,
    22. tempStr=new String(tempStr.getBytes("ISO-8859-1"),"GBK");
    23. //還原
    24. System.out.println(tempStr);
    25. }
    26. catch (Exception e) {
    27. System.err.println(e.getMessage());
    28. }
    29. }
    30. }

    輸出:
    GBK
    10
    -50
    utf-8
    15
    -26
    ???????ú??
    我是中國人
    


    小白

    評論

    # re: java中字符集的問題   回復  更多評論   

    2013-05-03 09:51 by pcenshao
    (GBK4個字節(jié),UTF-8是6個字節(jié))

    "UTF-8是6個字節(jié)",這個應該是筆誤吧,utf-8是變長編碼,您的文章上面也提到過.


    # re: java中字符集的問題   回復  更多評論   

    2014-08-21 14:39 by 彭永海

    # re: java中字符集的問題 [未登錄]  回復  更多評論   

    2014-11-13 16:30 by cooper
    最后一段代碼,為什么還能由ISO-8859-1還原為GBK,這點沒看懂。tempStr編碼為ISO-8859-1時不是10個63嗎,怎么能用GBK還原為中文?

    # re: java中字符集的問題 [未登錄]  回復  更多評論   

    2014-11-13 16:51 by cooper
    我明白了。如果是String tempStr = new String(str.getBytes("ISO-8859-1"), "GBK");這樣,能還原為中文嗎?
    主站蜘蛛池模板: 亚洲自偷自偷在线成人网站传媒| 久久免费精品视频| 最近中文字幕mv免费高清视频8| 久久狠狠躁免费观看| 免费看污成人午夜网站| 黄色网站软件app在线观看免费| 黄色一级毛片免费| 风间由美在线亚洲一区| 三年片免费高清版| 国产无人区码卡二卡三卡免费| 又粗又大又长又爽免费视频| 日韩va亚洲va欧洲va国产| 亚洲不卡视频在线观看| 免费一区二区无码视频在线播放| 久久久免费的精品| 国产成人高清精品免费软件| 亚洲人成网站影音先锋播放| 久久久久亚洲av无码专区蜜芽| 精品亚洲成A人无码成A在线观看| 一区二区视频在线免费观看| www视频在线观看免费| 亚洲高清偷拍一区二区三区| 亚洲精品乱码久久久久久下载| 亚洲成a人片在线观看中文app| 亚洲午夜精品一区二区公牛电影院 | 可以免费观看的毛片| 一二三四免费观看在线电影 | 免费国产黄线在线观看 | 久久久久久久综合日本亚洲| 亚洲国产人成在线观看| 免费无码午夜福利片 | 国产伦精品一区二区三区免费迷 | 亚洲中文字幕乱码熟女在线| 两个人日本免费完整版在线观看1| 在线永久看片免费的视频| 亚洲精品线路一在线观看| 亚洲日韩在线中文字幕第一页| 亚洲春色另类小说| 成人免费无码H在线观看不卡| 夭天干天天做天天免费看| 免费人成在线观看视频播放|