黑马程序员技术交流社区

标题: Java字符集编码问题 [打印本页]

作者: 我能学编程吗    时间: 2013-11-3 01:37
标题: Java字符集编码问题
本帖最后由 我能学编程吗 于 2013-11-3 01:44 编辑

看张孝详老师的基础视频,有讲到字符编码的知识,看着他的代码一模一样的敲都得不到相同结果,我简化后如下:
  1. //iso8859-1:西方国家使用的字符集
  2.                 System.setProperty("file.encoding","iso8859-1");//修改JAVA系统使用的默认字符编码为iso8859-1
  3.                 System.getProperties().list(System.out);//打印JAVA系统属性,从中查看file.encoding字符集
  4.                 String strChina="中国";
  5.                 byte [] buf=strChina.getBytes();//按默认字符集(即iso8859-1)对strChina编码,并转换成byte[]
  6.                 for(int k=0;k<buf.length;k++) {
  7.                         System.out.write(buf[k]);//这里传的必须是gbk编码的字节才能正确显示中文
  8.                 }
  9.                 System.out.println(); // 这里会自动调用flush,这样上面写的内容才会输出。说明System.out也是用了缓冲技术的。
复制代码
要求:                   1、修改Java默认的字符集编码:System.setProperty("file.encoding","iso8859-1");                           
          2、修改后String类的.getBytes();方法没指定字符集的话,就会使用默认的字符集编码,即上面设置的iso8859-1,所以上面代码中得到的buf编码应该是iso8859-1类型的
          3、System.out.write(buf[k])写到显示器上,这里写的是iso8859-1编码字节的数据,我们知道电脑的默认是使用gbk进行显示的,应该传gbk编码的数据才能正确显示。
          4、为什么电脑仍然输出的是正常的“中国”两字,说明我调用getBytes();方法的时候拿到的依然是gbk编码的字节数据,这是为什么啊?

麻烦大家跑一下这代码,看是输出“中国”,还是乱码。我看张孝详老师的可是会输出乱码的。







作者: 魏春旭    时间: 2013-11-3 08:31
{:soso_e127:}我还没看张老师的视频。
作者: 黄炳期    时间: 2013-11-3 10:52
魏春旭 发表于 2013-11-3 08:31
我还没看张老师的视频。

抓紧时间哦!

作者: --_.Is’攸稀    时间: 2013-11-3 12:30
没看张孝详老师的视频,也没学到那么深,所以就只能帮你运行一下了。

1.PNG (56.28 KB, 下载次数: 68)

1.PNG

作者: ∏艾力克斯※    时间: 2013-11-3 12:34
  1.         // iso8859-1:西方国家使用的字符集
  2.                 System.setProperty("file.encoding", "");// 修改JAVA系统使用的默认字符编码为iso8859-1
  3.                 System.getProperties().list(System.out);// 打印JAVA系统属性,从中查看file.encoding字符集
  4.                 String strChina = "中国";
  5.                 byte[] buf = strChina.getBytes();// 按默认字符集(即iso8859-1)对strChina编码,并转换成byte[]
  6.                 for (int k = 0; k < buf.length; k++) {
  7.                         System.out.write(buf[k]);// 这里传的必须是gbk编码的字节才能正确显示中文
  8.                 }
  9.                 System.out.println(); // 这里会自动调用flush,这样上面写的内容才会输出。说明System.out也是用了缓冲技术的。
复制代码
修改后,以不输入字符编码,或者干脆输入一个不存在的字符编码,均会输出结果“中国”,不乱码。
可见,System.setProperty("file.encoding", "123");这行代码已经失去作用,无效化。
本人推测,是运行环境所导致的问题,项目编码之类的可能会影响,但是没找到具体原因
作者: 黄炳期    时间: 2013-11-3 13:28
如果问题已经解决,请及时修改主题为“提问结束”。
修改主题的方法链接
http://bbs.itheima.com/thread-89313-1-1.html
作者: Mr.Z.Lee    时间: 2013-11-3 15:16
是正常的不是乱码
作者: 我能学编程吗    时间: 2013-11-3 16:31
黄炳期 发表于 2013-11-3 13:28
如果问题已经解决,请及时修改主题为“提问结束”。
修改主题的方法链接
http://bbs.itheima.com/thread-89 ...

还木有解决呢,具体原因不知道啊!
作者: 我能学编程吗    时间: 2013-11-3 19:27
本帖最后由 我能学编程吗 于 2013-11-3 19:36 编辑

终于,通过自己看源代码,我知道原因了:
查源代码,肯定是查返回byte字节的地方,点击getBytes()查看源码如下:
public byte[] getBytes() {
        return StringCoding.encode(value, offset, count);
}
这里看到调用了ecode方法,并且该方法没有任何的注释,所以只能再进源代码看了,点击进入encode方法的源代码如下:
static byte[] encode(char[] ca, int off, int len) {
        String csn = Charset.defaultCharset().name();
.....多句代码
}
此处第一句很明显是获取到了默认的字符编码的名称,查看JDK文档也有这个方法说明。把这句代码复制出来运行,可看到结果是GBK。为什么是GBK呢?只有再进源码再看了,点击defaultCharset方法进入源码,如下:
public static Charset defaultCharset() {
                if (defaultCharset == null) {
                        synchronized (Charset.class) {
                                java.security.PrivilegedAction pa = new GetPropertyAction("file.encoding");
                                String csn = (String)AccessController.doPrivileged(pa);
                                Charset cs = lookup(csn);
                                if (cs != null) defaultCharset = cs;
                                else defaultCharset = forName("UTF-8");
                        }
                }
                return defaultCharset;
        }
从上面源码可知,如果成员defaultCharset不为空,则直接返回,否则就是去获取一个字符集了,在代码中看到有new GetPropertyAction("file.encoding"),猜想它应该是要去获取Java环境属性中的“file.encoding”属性,
而代码:String csn = (String)AccessController.doPrivileged(pa);  一猜就是获取“file.encoding”属性的值,变量名应该是CharSetName的缩写吧。
而代码:Charset cs = lookup(csn); 很明显这句就是返名字为csn对应的字符集对象。Lookup方法没有注释说明。
而forName("UTF-8");是返回编码为"UTF-8"的字符集,这个方法有注释,也可在JDK文档中查看说明,再看这个方法的源码如下:
public static Charset forName(String charsetName) {
                Charset cs = lookup(charsetName);
                if (cs != null)
                        return cs;
                throw new UnsupportedCharsetException(charsetName);
        }
从上面源码可知,forName方法最终也是调用lookup方法返回一个字符集对象的。
综合上面的理解,可把上面的public static Charset defaultCharset()方法简化如下:
public static Charset defaultCharset() {
                if (defaultCharset == null) {
                                Charset cs = 获取Java环境变量"file.encoding"中指定的字符集
                                if (cs != null) defaultCharset = cs;
                                else defaultCharset = forName("UTF-8");
                        }
                }
                return defaultCharset;
        }
因此我们调用String csn = Charset.defaultCharset().name();时得到csn的值为GBK,说明上面的代码没有去获取"file.encoding"中指定的字符集,也就是说明上面代码中defaultCharset的值默认就有值了,而且是“GBK”,所以知道了:就算我改变了file.encoding变量的值,但是系统也不会查找这个变量的值来当默认的编码。因此,可以说张孝详老师的代码没有错,我们的代码也没有错,只是老师录那个视频时用的JDK版本比较低,而现在高版本的代码对这部份进行了修改。

空想没凭证,百度学习了一下反射,验证过程如下:
上面的源码中defaultCharset变量的声明的源码为:private static volatile Charset defaultCharset;  ,一看是静态的,而且私有的,这样就不能访问这个变量了,所以采用了反射技术拿到这个变量,并将它的值修改为 null 当defaultCharset == null 时,则上面public static Charset defaultCharset()方法就会执行  “获取Java环境变量"file.encoding"中指定的字符集”   的那些代码,运行结果证明,我是正确的,代码如下:
//修改JAVA系统使用的默认字符编码为iso8859-1
                System.setProperty("file.encoding","iso8859-1");

                // 通过反射技术,把Charset.defaultCharset的值修改为null
                Class clazz = Class.forName("java.nio.charset.Charset");// 获取Charset的字节码对象
                Field field = clazz.getDeclaredField("defaultCharset");        // 获取字段(包括私有的),这里获取的是私有的成员属性:defaultCharset
                field.setAccessible(true);                                                                // 设置私有字段可以被访问
                field.set(clazz, null);                                                                        // 修改defaultCharset成员的值为null

                // 如果Charset.defaultCharset == null,即会去获取环境变量file.encoding中指定的字符集
                String csn = Charset.defaultCharset().name();        // 获取默认的字符集的名字               
                System.out.println(csn);

                String strChina = "中国";
                byte[] buf = strChina.getBytes();        // 此处拿到的字节数据将是"iso8859-1"编码类型的
                System.out.write(buf);                                // 只有传GBK类型的才能正确显示中文字符,所以此处是乱码
                System.out.flush();

通过使用反射知识后,感觉自己的技术一下子就提升了,好牛 B 啊!
所以仔细一想,老师的方法已经过时,上面的代码中,不应该使用设置Java的环境变量file.encoding的值,来改变Java默认使用的编码,file.encoding默认是GBK,不要改变它,应该直接改变Charset.defaultCharset变量的方式来改变默认编码,在上面的代码中把修改file.encoding的代码注释,并增加如下代码:
Charset iso8859 = Charset.forName("ISO8859-1");        // 创建一个"ISO8859-1"的字符集
                field.set(clazz, iso8859);                                                                // 修改defaultCharset成员的值为"ISO8859-1"字符集

哇!好牛叉,我自己回答自己的问题,而且回答的这么详细,版主是否应该给加1分呢?

补充:Charset.defaultCharset变量的值是怎么默认就为GBK的呢?这个我就没有再去深究了,已经究不下去了,但是已经足够了,通过看源码已经让我学到许多东西了!
不过我可以猜想一下:Charset这个类是要比我们写的类先装进内存的,JVM自动调用了Charset.defaultCharset()方法,此方法会去查询Java环境变量file.encoding的值,此时的值是默认的GBK,所以JVM是首先把Charset.defaultCharset设置了GBKJVM自己的一些必须的类都启动完成了,才是启动我们写的类,虽然我们改变了file.encoding的值,但是Charset.defaultCharset的值已经初始化好了,已经是GBK了。


作者: ⋛⋌⋚JEEP    时间: 2014-7-15 21:53
lz牛逼,学习了:lol
作者: a191170796    时间: 2014-7-16 10:41
楼上的!跟着你有汤喝啊!
作者: nxp1986    时间: 2014-7-17 22:54
lz牛逼,我也跟着学习了
作者: 刘斌2014    时间: 2014-7-27 12:43
看了一下别人的解答 我也懂了
作者: 黑马-胡明    时间: 2014-7-27 12:59
厉害吧···
作者: 日光加蓝    时间: 2014-7-28 12:25
。。。。。。。。。。。。。。。。。。。。。。。。。
作者: 狐狸FMF    时间: 2014-8-13 10:37
java 真是  学无止境啊
作者: 狐狸FMF    时间: 2014-8-14 23:32
楼主真给力
作者: 黄小橙    时间: 2014-10-4 09:25
学习了!谢谢
作者: wujiemin    时间: 2014-10-7 09:18
求张泽华老师讲课时用的color.exe和size.exe工具
作者: wujiemin    时间: 2014-10-8 09:21
为了实现梦想;让自己活着更有意义 做自己有兴趣的事
作者: hamesksk    时间: 2015-3-16 00:54
是正常的不是乱码
作者: 吴阳    时间: 2015-3-16 14:00
提前学习了
作者: 等风的车    时间: 2015-3-16 14:01
我的也是乱码
作者: yanfeng    时间: 2015-7-19 20:39
张孝祥老师的视频不错啊
作者: yuanyuemao88    时间: 2015-8-2 17:38
还在看基础视频,苦逼啦
作者: cloud1991    时间: 2015-9-15 15:05
表示不太懂,学习一下
作者: csc    时间: 2015-12-25 01:37
多谢楼主
作者: 存在着的天空    时间: 2015-12-25 21:06
JAVA无论是什么时候都有很大的用处




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2