Java中涉及的編碼知識

發發牢騷

在工作中遇到不少Java編碼問題,每次解決亂碼問題,都花了較長的時間,非常影響工作效率!
在工作中會經常遇到各種各樣的問題,每次一個問題總是搗騰了很久,然後就解決了,但其實沒有弄明白問題的本質。當下次遇到類似的問題,還是繼續折騰,非常浪費時間。
想要高效解決編碼問題,當然要究其根源。以後遇到類似問題,可以順藤摸瓜,輕鬆解決!

典型問題

  • Java採用哪種字符集
  • Java中一個字符需要幾個字節存儲
  • String.getBytes()方法哪種編碼方式?IOS-8895-1、還是UTF-8?
  • 亂碼產生的原因

如果能夠回答以上問題,那麼這篇文章可以Pass了!

將Unicode字符串轉化成UTF-8、ISO-8859-1字符串

Java採用Unicode字符集,一個字符用兩個字節存儲。

以下是程序片段:將Unicode字符串轉化成UTF-8、ISO-8859-1字符串

    String cnName = "I am 小佳";
    //Unicode字符集        
    System.out.println(printHexString(cnName.toCharArray()));
    //ISO-8859-1編碼       
    System.out.println(printHexString(cnName.getBytes("ISO-8859-1")));
    //UTF-8編碼 
    System.out.println(printHexString(cnName.getBytes("UTF-8")));
    /**
     * 字節數組轉換成16進制
     * @param b
     * @return
     */
    private  String printHexString( byte[] b) {
        String a = "";
        for (int i = 0; i < b.length; i++) {
            String hex = Integer.toHexString(b[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }

            a = a+hex;
        }
        return a;
    }

    /**
     * 字符數組轉化成16進制
     * @param b
     * @return
     */
    private  String printHexString(char[] b) {
        String a = "";
        for (int i = 0; i < b.length; i++) {
            String hex = Integer.toHexString(b[i] & 0xFFFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            a = a+hex;
        }
        return a;
    }

字符串”I am 小佳”,由Unicode轉換成UTF-8和ISO-8859-1,得到不同的字節(16進製表示),主要區別如下:

  1. 對於英文字符,無論採用何種編碼方式,得到的編碼結果相同。所以,英文字符不存在亂碼問題
  2. 對於中文字符,不同的編碼方式得到不同的結果;Unicode用2個字節存儲中文,UTF-8用3個字節存儲中文
  3. ISO-8859-1不支持中文,任何中文,採用該編碼,都會轉換成3F(3F映射的字符爲’?’); 所以當中文用ISO-8859-1編碼時,會生成一些列帶問號的亂碼’????????????????’
編碼方式 I a m
Unicode 49 20 61 6D 20 5C0F 4F73
ISO-8859-1 49 20 61 6D 20 3F 3F
UTF-8 49 20 61 6D 20 E5B08F E4BDB3

亂碼的產生

採用不同的編碼方式進行 編碼 和 解碼 , 是生成亂碼的根源。下面分析兩種常見的亂碼

    String cnName = "I am 小佳";
    //採用ISO-8859-1編碼、UTF-8解碼
    String iso88591Str = new String (cnName.getBytes("ISO-8859-1"), "UTF-8");
    //採用UTF-8編碼、ISO-8859-1解碼
    String utf8Str = new String (cnName.getBytes("UTF-8"), "ISO-8859-1");             
    System.out.println(iso88591Str);//打印結果,亂碼:I am ??
    System.out.println(utf8Str);//打印結果,亂碼:I am 小佳
源字符串 編碼 解碼 轉換結果
I am 小佳 UTF-8 ISO-8859-1 I am 小佳
I am 小佳 ISO-8859-1 UTF-8 I am ??

產生亂碼”I am 小佳”的原因是:ISO-8859-1只能對單個字節編碼
產生亂碼”I am ??”的原因是:ISO-8859-1會把所有的中文字符轉化成3F,3F映射的字符爲’?’

getBytes()的編碼方式

String.getBytes()採用操作系統默認的編碼方式,請看getBytes()源碼
獲取默認編碼在這個方法: defaultCharset(),該方法從關鍵
“file.encoding”獲取默認編碼方式
題外話:defaultCharset方法值得研究,該方法是線程安全的,並且用了緩存!
如果你使用getBytes(),發生了中文亂碼問題, 則需要設置file.encoding爲UTF-8, 即可解決亂碼問題

    public byte[] getBytes() {
        return StringCoding.encode(value, 0, value.length);

    }
    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;
        }
    }
    public static Charset defaultCharset() {
        if (defaultCharset == null) {
            synchronized (Charset.class) {
                //從操作系統獲取默認得編碼方式
                String csn = AccessController.doPrivileged
                    new GetPropertyAction("file.encoding"));
                Charset cs = lookup(csn);
                if (cs != null)
                    defaultCharset = cs;
                else
                    defaultCharset = forName("UTF-8");
            }
        }
        return defaultCharset;
    }

總結

我這裏只是拋磚引玉,講述了Unicode轉換UTF-8、ISO-8859-1的過程,以及亂碼問題產生的根源。
如果對編碼問題感興趣的話,可以進一步探討GBK、GB2313、UTF-8的編碼轉換是否會產生亂碼
另外,編碼問題很考驗思維深度,有不少面試官會專門出編碼的題目。所以,編碼問題值得研究!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章