zoukankan      html  css  js  c++  java
  • 深入解析java String中getBytes()的编码问题

    转载出处:http://www.cnblogs.com/Joanna-Yan/p/6900536.html 

    Java服务器后台在和Android端App通信时,遇到了两端关于用MD5加密同一包含中文的字符串结果不一致的问题。

    具体问题描述:

    Java服务器后台和Android端AS用了同一个MD5的工具类,且两边项目的默认编码都是UTF-8 ,加密纯英文数字的字符串时,结果一致,对同一包含中文的字符串加密,发现结果不一样,这是为什么呢?

    工具类MD5Util代码如下:

    复制代码
    public class MD5Util {
        /**
         * 将byte数组转化为16进制输出
         * @param bytes
         * @return
         */
        public static String convertByteToHexString(byte[] bytes){
            String result="";
            for (int i = 0; i < bytes.length; i++) {
                int temp=bytes[i]&0xff;
                String tempHex=Integer.toHexString(temp);
                if(tempHex.length()<2){
                    result+="0"+tempHex;
                }else{
                    result+=tempHex;
                }
            }
            return result;
        }
        
        /**
         * MD5加密
         * @param message
         * @return
         * @throws UnsupportedEncodingException 
         */
        public static String md5Jdk(String message) throws UnsupportedEncodingException{
            String temp="";
            try {
                MessageDigest md5Digest=MessageDigest.getInstance("MD5");
                byte[] encodeMD5Digest=md5Digest.digest(message.getBytes());
                temp=convertByteToHexString(encodeMD5Digest);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            return temp;
        }
    }
    复制代码

     最后问题锁定在:

    IDE的项目默认编码和平台运行环境的编码不是一回事。

    改成:message.getBytes("UTF-8");后能解决中文加密不一致的问题。

    下面,就String的getBytes()方法深入了解下。

    String的getBytes()方法是得到一个字符串的字节数组,但特别要注意的是,本方法将返回该操作系统默认的编码格式的字节数组。如果你在使用这个方法时不考虑这一点,你会发现在一个平台上运行良好的系统,放到另一台机器上会产生意想不到的问题。

    比如下面的程序:

    复制代码
    public class TestCharset {
        public static void main(String[] args) { 
            new TestCharset().execute(); 
        } 
        
        private void execute() { 
            String s = "Hello!你好!"; 
            byte[] bytes = s.getBytes();
            System.out.println("bytes lenght is:" + bytes.length); 
        } 
    }
    复制代码

    在一个中文WindowsXP系统下,运行时,结果为:

    bytes lenght is:12

    但是如果放到了一个英文的UNIX环境下运行:

    $ java TestCharset bytes lenght is:9

    如果你的程序依赖于该结果,将在后续操作中引起问题。为什么在一个系统中结果为12,而在另外一个却变成了9?上面已经提到了,该方法是和平台(编码)相关的。

    在中文操作系统中,getBytes方法返回的是一个GBK或GB2313的中文编码的字节数组,其中中文字符各占两个字节。而在英文平台中,一般的默认编码是“ISO-8859-1”,每个字符都只取一个字节(而不管是否非拉丁字符)。

    Java中的编码支持

    Java是支持多国编码的,在Java中,字符都是以Unicode进行存储的,比如,“你”字的Unicode编码是“4f60”,我们可以通过下面的实验代码来验证:

    复制代码
    public class TestCharset { 
        public static void main(String[] args) { 
            char c = '你'; 
            int i = c; 
            System.out.println(c); 
            System.out.println(i); 
        } 
    }
    复制代码

    不管你在任何平台上执行,都会有相同的输出:

    20320

    20320就是Unicode “4f60”的整数值。其实,你可以反编译上面的类,可以发现在生产的.calss文件中字符“你”(或者其它任何中文字串)本身就是以Unicode编码进行存储的:

    char c = '/u4F60'; ... ...

    即使你知道了编码的编码格式,比如:

    javac -encoding GBK TestCharset.java

    编译后生成的.class文件中仍然是以Unicode格式存储中文字符或字符串的。

    所以。为了避免这种问题,建议大家都在编码中使用String.getBytes(String charset)方法。

    下面我们将从字串分别提取ISO-8859-1和GBK两种编码格式的字节数组,看看会有什么结果:

    复制代码
    public class TestCharset {  
        public static void main(String[] args) {  
            new TestCharset().execute();  
        }  
      
        private void execute() {  
            String s = "Hello!你好!";  
            byte[] bytesISO8859 = null;  
            byte[] bytesGBK = null;  
            try {  
                bytesISO8859 = s.getBytes("iso-8859-1");  
                bytesGBK = s.getBytes("GBK");  
            } catch (java.io.UnsupportedEncodingException e) {  
                e.printStackTrace();  
            }  
            System.out.println("-------------- /n 8859 bytes:");  
            System.out.println("bytes is: " + arrayToString(bytesISO8859));  
            System.out.println("hex format is:" + encodeHex(bytesISO8859));  
            System.out.println();  
            System.out.println("-------------- /n GBK bytes:");  
            System.out.println("bytes is: " + arrayToString(bytesGBK));  
            System.out.println("hex format is:" + encodeHex(bytesGBK));  
        }  
      
        public static final String encodeHex(byte[] bytes) {  
            StringBuffer buff = new StringBuffer(bytes.length * 2);  
            String b;  
            for (int i = 0; i < bytes.length; i++) {  
                b = Integer.toHexString(bytes[i]);  
                // byte是两个字节的, 而上面的Integer.toHexString会把字节扩展为4个字节  
                buff.append(b.length() > 2 ? b.substring(6, 8) : b);  
                buff.append(" ");  
            }  
            return buff.toString();  
        }  
      
        public static final String arrayToString(byte[] bytes) {  
            StringBuffer buff = new StringBuffer();  
            for (int i = 0; i < bytes.length; i++) {  
                buff.append(bytes[i] + " ");  
            }  
            return buff.toString();  
        }  
    } 
    复制代码

    执行结果:

    复制代码
    -------------- /n 8859 bytes:
    bytes is: 72 101 108 108 111 33 63 63 63 
    hex format is:48 65 6c 6c 6f 21 3f 3f 3f 
    
    -------------- /n GBK bytes:
    bytes is: 72 101 108 108 111 33 -60 -29 -70 -61 -93 -95 
    hex format is:48 65 6c 6c 6f 21 c4 e3 ba c3 a3 a1 
    复制代码

    可见,在s中提取的8859-1格式的字节数组长度为9,中文字符都变成了“63”,ASCII码为63的是“?”,一些国外的程序在国内中文环境下运行时,经常会出现乱码,上面布满了“?”,就是因为编码没有进行正确处理的结果。

    而提取的GBK编码的字节数组中正确得到了中文字符的GBK编码。字符“你”、“好”、“!”的GBK编码分别是:“c4e3”、“bac3”、“a3a1”。得到了正确的以GBK编码的字节数组,以后需要还原为中文字串时,可以使用下面方法:

    new String(byte[] bytes, String charset)

    ==================================================================================================================

    String.getBytes(Stringdecode)方法, 会根据指定的decode编码返回某字符串在该编码下的byte数组表示,如:
    byte[] b_gbk = "中".getBytes("GBK");
    byte[] b_utf8 = "中".getBytes("UTF-8");
    byte[] b_iso88591 = "中".getBytes("ISO8859-1");
    将分别返回"中"这个汉字在GBK、UTF-8和ISO8859-1编码下的byte数组表示,此时

    b_gbk的长度为2,

    b_utf8的长度为3,

    b_iso88591的长度为1。

     

    new String(byte[], decode)方法,而与getBytes相对的,可以通过new String(byte[], decode)的方式来还原这个"中"字,

    这个new String(byte[],decode)实际是使用指定的编码decode来将byte[]解析成字符串.
    String s_gbk = new String(b_gbk,"GBK");
    String s_utf8 = new String(b_utf8,"UTF-8");
    String s_iso88591 = new String(b_iso88591,"ISO8859-1");


    通过输出s_gbk、s_utf8和s_iso88591,会发现s_gbk和s_utf8都是"中",而只有s_iso88591是一个不被识别的字符(可以理解为乱码),为什么使用ISO8859-1编码再组合之后,无法还原"中"字?原因很简单,因为ISO8859-1编码的编码表根本就不包含汉字字符,当然也就无法通过"中".getBytes("ISO8859-1");来得到正确的"中"字在ISO8859-1中的编码值了,所以,再通过newString()来还原就更是无从谈起。
    因此,通过String.getBytes(Stringdecode)方法来得到byte[]时,一定要确定decode的编码表中确实存在String表示的码值,这样得到的byte[]数组才能正确被还原。

     

    注意:

    有时候,为了让中文字符适应某些特殊要求(如httpheader要求其内容必须为iso8859-1编码),可能会通过将中文字符按照字节方式来编码的情况,如:
    String s_iso88591 = newString("中".getBytes("UTF-8"),"ISO8859-1"),这样得到的s_iso8859-1字符串实际是三个在ISO8859-1中的字符,

    在将这些字符传递到目的地后,目的地程序再通过相反的方式Strings_utf8 = newString(s_iso88591.getBytes("ISO8859-1"),"UTF-8")来得到正确的中文汉字"中",这样就既保证了遵守协议规定、也支持中文

    我是一个小小的搬运工,让米粒变成仓廪
  • 相关阅读:
    龙井和碧螺春的功效与作用
    064 01 Android 零基础入门 01 Java基础语法 08 Java方法 02 无参带返回值方法
    063 01 Android 零基础入门 01 Java基础语法 08 Java方法 01 无参无返回值方法
    062 01 Android 零基础入门 01 Java基础语法 07 Java二维数组 01 二维数组应用
    061 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 08 一维数组总结
    060 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 07 冒泡排序
    059 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 06 增强型for循环
    058 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 05 案例:求数组元素的最大值
    057 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 04 案例:求整型数组的数组元素的元素值累加和
    056 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 03 一维数组的应用
  • 原文地址:https://www.cnblogs.com/ricehome/p/14152518.html
Copyright © 2011-2022 走看看