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的编码转换是否会产生乱码
另外,编码问题很考验思维深度,有不少面试官会专门出编码的题目。所以,编码问题值得研究!

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