本帖最后由 我能学编程吗 于 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设置了GBK。JVM自己的一些必须的类都启动完成了,才是启动我们写的类,虽然我们改变了file.encoding的值,但是Charset.defaultCharset的值已经初始化好了,已经是GBK了。
|