以下为我自己的学习总结(部分是网络上资源),并不能确定百分之百正确,请读者自己多思考验证。我会及时更新笔记。如您发现错误,请帮忙指出,谢谢。
------------------------------------------------------------------------------------------------------------------------
在JAVA源文件-->JAVAC-->Class-->Java-->getBytes()-->new String()-->显示的过程中,每一步都有编码的转换过程,这个过程总是存在的,只是有的时候用默认的参数进行。
1. JAVAC是以系统默认编码读入源文件,然后按UNICODE进行编码的。可以通过指定编码方式改变Javac读入源文件的编码方式。
- javac -encoding GBK Test.java
2. 在JAVA运行的时候,JAVA也是采用UNICODE编码的,并且默认输入和输出的都是操作系统的默认编码。
也就是说在new String(bytes[,encode])中,系统认为输入的是编码为encode的字节流,换句话说,如果按encode来翻译bytes才能得到正确的结果,这个结果最后要在JAVA中保存,它还是要从这个encode转换成Unicode,也就是说有bytes-->encode字符-->Unicode字符的转换;而在String.getBytes([encode])中,系统要做一个Unicode字符-->encode字符-->bytes的转换。【巨重要】(我们在Java程序中new一个String,参数是字节数组,则过程是先将字节数组按系统默认编码字符集GBK转换成GBK格式的字节数组,再转成Unicode编码的字节数组,JVM中都是Unicode编码的字符串;而我们对一个String字符串用getBytes()方法获取其字节数组,是先把该字符串的Unicode编码的字节数组转换成系统默认的GBK编码的字节数组)【巨重要】
即:我们用String类的构造函数生成一个字符串,参数是字节数组,过程是:字节数组--->GBK字节数组--->Unicode字节数组
我们把一个String变量用getBytes( )方法得到字节数组,过程是:该字符串的Unicode字节数组--->GBK字节数组
--------------------------------------------------------------------------------------------------
上面这一段文字巨重要,以下是我的笔记:
我们在文本编辑器(如eclipse中)写的Java代码,我们可以指定这个源文件在硬盘上存储时的编码格式,比如GBK、UTF-8,然后当我们用javac命令编译该源文件的时候,默认用操作系统默认字符集编码读取,中文Windows下就是GBK(GB2312),如果你保存源文件选择UTF-8保存,而读取时没有指定编码格式,系统会用GBK编码读取,就会出现乱码,报错。如果我们保存时用GBK保存、javac编译时又用GBK读取,就不会报错,然后该源文件被编译成class文件(当我打开这个class文件可以看到,class文件中较多的字符是都可以看懂的,另:比如我在源文件中定义了一个字符串”中文“,以GBK存储,当我将此源文件编译之后,生成的class文件中,”中文“二字竟然是按照UTF-8编码存放的,值是E4B8ADE69687,而”你“的UTF-8编码是E4BD)。
看上面的程序,我在源程序中定义了”中文“这个字符串,然后我在程序中输出每个字符的值,得到的是”中“这个字符的UNICODE编码是20013,”文”的UNICODE编码是25991,即:“在Java程序运行中,字符都是以Unicode存储的”这句话意思就是,我定义了“中文”这个字符串,在程序在JVM中运行的时候,这个字符串是以Unicode编码格式存储的。
虽然在源程序中我以UTF-8、GBK存储源文件了,虽然生成的class文件中“中文”是以UTF-8存储的,但在程序在JVM中运行的时候“中文”是以Unicode编码存储的。
还有一点:
如果我在上面的程序中对字符串变量s这样:s.getBytes("UTF-8");其实是把“中文”二字从Unicode编码转换成UTF-8编码的字节数组(说到这里是不是对这个函数理解的更透彻了,欧耶!),然后可以对字节数组再进行编码生成字符串!
还有一点:如下面这个十分简单的程序:
我们定义了一个字符串,然后用System.out.println输出这个字符串,这个过程中发生了什么?其实是这样的:“ab中文”字符串在JVM中是Unicode编码存储的,当我们输出的时候,System.out.println会将“ab中文”的Unicode编码转换成系统默认的GBK编码的字节数组送到输出流里,终端(即Java控制台吧)里会对输出的流里的字节按照终端的编码进行decode得到字符即我们能看懂的“ab中文”。【我说的对不】
---------------------------------------------------------------------------------------------------------
3. Java中的编码支持
Java是支持多国编码的,在Java中,字符都是以Unicode进行存储的,比如,“你”字的Unicode编码是“4f60”,我们可以通过下面的实验代码来验证:
- 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”的整数值。其实,你可以反编译上面的类,可以发现在生成的.class文件中字符“你”(或者其它任何中文字串)本身就是以Unicode编码进行存储的:char c = '\u4F60';【左边说的不正确,生成的class文件“你”是以UTF-8存储的,值为E4BD】
4. 为了避免这种问题,建议大家都在编码中使用String.getBytes(String charset)方法。
- 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"); 【"Hello!你好!"在内存中是Unicode编码的,即便用iso-8859-1编码,不也应该全部进行编码吗,为什么下面的输出的编码少了那么多?】 (解释:ISO-8859-1对每个字符使用一个字节编码,"Hello!你好!"共9个字符,在JVM中英文部分“Hello!"的Unicode编码可转换成ISO-8859-1编码,但汉字“你好!”无法转换成ISO-8859-1,用问号代替了!有一点:Unicode编码用两个字节表示任意字符,“中文!”的Unicode编码是4个字节,但是转换成ISO-8859-1的时候成了2个字节,唉,其实Unicode转ISO-8859-1或转UTF-8都是以两个字节为单位的,转ISO-8859-1两个字节转换成一个,转UTF-8两个字节转成3个字节)
- 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] + " "); 【byte数组中的默认都是ASCII码值吗?】
- }
- return buff.toString();
- }
- }
执行上面程序将打印出:
-------------- 8859 bytes: bytes is: 72 101 108 108 111 33 63 63 63【8859编码对最后三个中文无法识别,所以用三个问号表示,共用9个字节】 hex format is:48 65 6c 6c 6f 21 3f 3f 3f -------------- GBK bytes: bytes is: 72 101 108 108 111 33 -60 -29 -70 -61 -93 -95 即:"Hello!你好!"(GBK编码后的字节值,共12个字节,GBK对6个英文只用一个字节编码,对3个中文用两个字节编码,所以共用12个字节) hex format is:48 65 6c 6c 6f 21 c4 e3 ba c3 a3 a1 |
可见,在s中提取的8859-1格式的字节数组长度为9,中文字符都变成了“63”,ASCII码为63的是“?”,一些国外的程序在国内中文环境下运行时,经常出现乱码,上面布满了“?”,就是因为编码没有进行正确处理的结果。
把JVM内存中unicode形式的String按encoding制定的编码,转成字节流 【将Unicode编码的数值转换成charset指定的编码的编码值】
使用给定的 charset 将此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。
通过使用指定的 charset解码指定的 byte数组,构造一个新的 String。
1.JVM内部的String,Char都是用unicode存储(没有任何编码),比如:
"分"的unicode=20998(十进制)String=[20998],String中有1个char
"分享"的unicode=20998,20139 (十进制)String=[20998,20139],String中有2个char
无论系统编码是什么,"分"这个字在JVM中都是20998