很久以前听教学视频,里面讲到Java采用的默认编码是ISO-8859-1,一直记着。

但是最近重新看IO流的时候,惊讶地发现,在不指定字符编码的情况下,FileReader居然可以读取内容为中文的文本文件。要知道ISO-8859-1可是西欧字符集,怎么能包含中文呢?于是百度了一下关键词“IOS-8859-1显示中文”,结果很多人都有这个疑惑。

代码如下:


  1. package day170903;
  2. import java.io.*;
  3. public class TestDecoder {
  4. public static void main(String[] args) {
  5. FileReader fr = null;
  6. try {
  7. fr = new FileReader("G:/io/hello.txt");
  8. int len = 0;
  9. while((len=fr.read())!=-1) {
  10. System.out.println((char)len);
  11. }
  12. } catch (FileNotFoundException e) {
  13. e.printStackTrace();
  14. } catch (IOException e) {
  15. e.printStackTrace();
  16. } finally {
  17. try {
  18. if(fr!=null) {
  19. fr.close();
  20. }
  21. } catch (IOException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. }
  26. }

事情的真相是什么呢?

编码一般是在构造方法处指定的,于是查看一下FileReader的构造方法。也是奇葩,以前没怎么注意过,FileReader竟然没有可以指定字符编码的构造方法。而且仅仅是简单地从InputStreamReader继承,并没有重写或扩展任何方法。这可能是历史上最吝啬的子类,完全就是啃老族。

不过好在Java的文档注释写得很给力,在FileReader这个类的开头有下面一段文档注释(中文部分为我劣质的翻译):

/**

* Convenience class for reading character files.  The constructors of this

* class assume that the default character encoding and the default byte-buffer

* size are appropriate.  To specify these values yourself, construct an

* InputStreamReader on a FileInputStream.

*

*这是一个很方便的读取字符文件(文本文件)的类。

*这个类的构造方法假设默认的字符编码和默认的缓存数组大小是合适的(满足需要的)。

*假如你想自己指定字符编码和缓存数组的大小,

*请使用基于FileInputStream的InputStreamReader类。

* <p><code>FileReader</code> is meant for reading streams of characters.

* For reading streams of raw bytes, consider using a

* <code>FileInputStream</code>.

*

*FileReader是设计为用来读取字符流的。

*想要读取原始的字节流的话,可以考虑使用FileInputStream

* @see InputStreamReader

* @see FileInputStream

*

* @author      Mark Reinhold

* @since       JDK1.1

*/

所以,设计者已经在文档注释中讲明白了这么设计的原因。但是对于我们来说,现在比较重要的是这个所谓的默认的字符编码是什么。

这个时候我们来看一下我们使用的FileReader中的那个构造方法的具体内容。


  1. public FileReader(String fileName) throws FileNotFoundException {
  2. super(new FileInputStream(fileName));
  3. }

FileReader继承自InputStreamReader,调用了InputStreamReader的接受InputStream类型的形参的构造方法,也就是下面这个。


  1. public InputStreamReader(InputStream in) {
  2. super(in);
  3. try {
  4. sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
  5. } catch (UnsupportedEncodingException e) {
  6. // The default encoding should always be available
  7. throw new Error(e);
  8. }
  9. }

当然InputStreamReader的这个构造方法又调用了其父类Reader的下面的构造方法。


  1. protected Reader(Object lock) {
  2. if (lock == null) {
  3. throw new NullPointerException();
  4. }
  5. this.lock = lock;
  6. }

在这里,它只是把得到的InputStream对象赋值给成员变量lock(看lock这个成员变量的文档注释的话,大概知道它是用来保证同步的),并没有说到字符编码的事。

既然通过super(in)向上查找到父类Reader的构造方法也没有发现默认字符编码的踪迹,那么这条道就到头了。接下来应该看的是super(in)下面的代码,也就是那个异常捕捉语句块。主体语句只有下面一行内容。

sd = StreamDecoder.forInputStreamReader(in, this, (String)null);

仔细看FileReader和其它IO流的代码的话会发现,很多输入流的读取功能(read及其重载方法)都是通过这个StreamDecoder完成的,这是后话。在Eclipse里面直接查看这个

StreamDecoder的源码是不行的,需要去openjdk上找。

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/nio/cs/StreamDecoder.java

上面异常捕捉语句块主体部分调用的是StreamDecoder的forInputStreamReader方法,对应的代码如下:


  1. public static StreamDecoder forInputStreamReader(InputStream in,
  2. Object lock,
  3. String charsetName)
  4. throws UnsupportedEncodingException
  5. {
  6. String csn = charsetName;
  7. if (csn == null)
  8. csn = Charset.defaultCharset().name();
  9. try {
  10. if (Charset.isSupported(csn))
  11. return new StreamDecoder(in, lock, Charset.forName(csn));
  12. } catch (IllegalCharsetNameException x) { }
  13. throw new UnsupportedEncodingException (csn);
  14. }

其实调用的时候,传递的第三个参数是字符串形式的null,这个其实就是我们要找的默认字符编码。

我们要找的是默认字符编码,其它代码不必深究。第一行是说把接收到的第三个参数赋值给csn(局部变量:字符编码),当然了,这个是被InputStreamReader的带字符编码参数的构造方法调用的时候才有意义的。没有指定字符编码的构造方法调用StreamDecoder的forInputStreamReader的时候传递是null。所以接下来的if语句判断就成立了,那么csn这个变量得到的就是Charset.defaultCharset().name(),见名知意,即默认字符编码。

接下来就要看Charset这个类的defaultCharset方法的返回值——Charset对象的name()方法的返回值是什么了。说起来有点绕,其实就是找里面的默认字符编码。


  1. public static Charset defaultCharset() {
  2. if (defaultCharset == null) {
  3. synchronized (Charset.class) {
  4. String csn = AccessController.doPrivileged(
  5. new GetPropertyAction("file.encoding"));
  6. Charset cs = lookup(csn);
  7. if (cs != null)
  8. defaultCharset = cs;
  9. else
  10. defaultCharset = forName("UTF-8");
  11. }
  12. }
  13. return defaultCharset;
  14. }

这代码看起来很费劲,而且接着又要看其它代码。最终结果是这个所谓的默认字符编码,其实就是JVM启动时候的本地编码。

这个要查看的话,就在对应的项目上点击右键,选择Properties选项,在弹出的属性窗口中,可以看到当前项目在JVM中运行时候的默认字符编码。对于咱们中国人来说,一般都是“GBK”,不过可以根据需要从下拉框选择。

所以开头那个疑问,完全是因为不知道默认的编码其实是GBK而产生的误解。反过来测试一下就好了,先用OutputStreamWriter往文件中写入下面一句法语

Est-ce possible que tu sois en train de penser à moi lorsque tu me manques?

我在想你的时候,你会不会也刚好正在想我?

写入的时候指定字符编码为ISO-8859-1,然后用InputStreamReader读取,读取的时候不指定字符编码(即采用默认字符编码)。那么,假如不能正确还原这句话,就说明默认的字符编码并不是ISO-8859-1。


  1. package day170903;
  2. import java.io.*;
  3. public class TestDefaultCharEncoding {
  4. public static void main(String[] args) {
  5. InputStreamReader isr = null;
  6. OutputStreamWriter osw = null;
  7. try {
  8. osw = new OutputStreamWriter(new FileOutputStream("G:/io/ISO-8859-1.txt"),"ISO-8859-1");
  9. isr = new InputStreamReader(new FileInputStream("G:/io/ISO-8859-1.txt"));
  10. char[] chars = "Est-ce possible que tu sois en train de penser à moi lorsque tu me manques?".toCharArray();
  11. osw.write(chars);
  12. osw.flush();
  13. int len = 0;
  14. while((len=isr.read())!=-1) {
  15. System.out.print((char)len);
  16. }
  17. } catch (UnsupportedEncodingException | FileNotFoundException e) {
  18. e.printStackTrace();
  19. } catch (IOException e) {
  20. e.printStackTrace();
  21. } finally {
  22. try {
  23. if(isr!=null) {
  24. isr.close();
  25. }
  26. if(osw!=null) {
  27. osw.close();
  28. }
  29. } catch (IOException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. }

输出结果是:

Est-ce possible que tu sois en train de penser ? moi lorsque tu me manques?

大部分都正确还原了,因为法语中大部分也是英文字母。但是那个法语特有的(相比于英语)à 读出来以后无法识别,变成了问号。

假如默认编码真的是ISO-8859-1,那么读取是完全没有问题的。现在有问题,正好说明默认编码不是ISO-8859-1。

基本上到这儿就完事了,但是还要说一句。虽然我们可以很方便地知道在不指定字符编码的情况下,JVM将会采用什么编码,但是还是建议采用字符类的时候加上字符编码,因为写清楚字符编码可以让别人明白你的原意,而且能避免代码转手后换了一个开发工具后可能出现的编码异常问题。

-----------------------------------------------------------------------------------end

可以因为无能为力而选择视而不见,但是不要因为心知肚明而忘了开口。

FileReader采用的默认编码的更多相关文章

  1. hibernate自动建表采用UTF-8字符编码

    hibernate自动建表采用UTF-8字符编码 hibernate建表默认为UTF-8编码 >>>>>>>>>>>>>& ...

  2. eclipse 设置 默认编码为 utf-8

    学习javaweb时,开发工具都采用utf-8的编码方式,给eclipse设置默认编码为utf-8的编码方法 菜单 Window -> preference -> General -> ...

  3. MyEclipse中把JSP默认编码改为UTF-8

    在MyEclispe中创建Jsp页面,Jsp页面的默认编码是“ISO-8859-1”,如下图所示: 在这种编码下编写中文是没有办法保存Jsp页面的,会出现如下的错误提示: 因此可以设置Jsp默认的编码 ...

  4. [Linux] 修改系统默认编码

    locale 命令 locale 命令用以设置程序运行的语言环境. locale 设置语言环境的命名规则为 Language_area.charset,例如 en_US.utf8 表示语言为英语,地区 ...

  5. MySql默认编码所造成的乱码麻烦1.222

    1.前言 MySQL在安装时,最后的一步,会让你选择MySQL服务器及客户端.数据库.连接接口的默认编码.通常可选择 UTF8和GB2312. 但是,如果你选择了utf8的时候,恰好你要从另一个数据库 ...

  6. python3.x设置默认编码(sys.stdout.encoding和sys.defaultencoding)

    查了一会资料得出的结论是如果你用的是python3.x,那么就最好别去设置sys.defaultencoding或者sys.stdout.encoding记住在需要编码的时候用encode,解码的时候 ...

  7. myeclipse学习总结一(在MyEclipse中设置生成jsp页面时默认编码为utf-8编码)

    1.每次我们在MyEclispe中创建Jsp页面,生成的Jsp页面的默认编码是"ISO-8859-1".在这种情况下,当我们在页面中编写的内容存在中文的时候,就无法进行保存.如下图 ...

  8. CentOS7下mysql5.6修改默认编码

    参考原文教程:Centos7下修改mysql5.6编码方式 解决网站中文显示问号 解决办法: 修改MySQL数据库字符编码为UTF-8,UTF-8包含全世界所有国家需要用到的字符,是国际编码. 具体操 ...

  9. 转载--改变ubuntu默认编码为GBK

    在Ubuntu支持中文后(方法见上篇文章),默认是UTF-8编码,而Windows中文版默认是GBK编码.为了一致性,通常要把Ubuntu的默认编码改为GBK.当然你也可以不改,但这会导致我们在两个系 ...

随机推荐

  1. 【MemSQL Start[c]UP 3.0 - Round 1 E】Desk Disorder

    [链接]h在这里写链接 [题意] 有N个人. 2N个座位. 现在告诉你这N个人它们现在的座位.以及它们想去的座位. 每个人可以去它们想去的座位或者就站在原地不动. 新的座位和旧的座位,都不允许一个座位 ...

  2. C#学习笔记——常量、字段以及事件

    一 常量与字段 (一) 常量 常量总是被视为静态成员,而不是实例成员.定义常量将导致创建元数据.代码引用一个常量时,编译器会在定义常量的程序集的元数据中查找该符号,提取常量的值,并将值嵌入IL中.由于 ...

  3. 在 Swift 项目中实现侧滑菜单-利用 SWRevealViewController

    你可以完全自己手动写一个侧滑菜单,但是现在在 GitHub 上面已经有很多免费的开源库了,如果不是有很特别的需求,大可不必新建一个轮子. 在这里我使用的这个第三方库名字叫做 SWRevealViewC ...

  4. Redis学习笔记--Hash(五)

    Redis的数据是通过key-value的方式存储的,对于value的数据类型有字符串.Hash.list.set.sortedSet在redis命令语句中,语句是忽略大小写的,但是key是不可以忽略 ...

  5. EL表达式.md

    操作符 描述 . 访问一个Bean属性或者一个映射条目 [] 访问一个数组或者链表的元素 ( ) 组织一个子表达式以改变优先级 + 加 - 减或负 * 乘 / or div 除 % or mod 取模 ...

  6. 8、linux下输入子系统

    input_sync(button_dev);    /*通知接收者,一个报告发送完毕*/ 参考:http://www.51hei.com/bbs/dpj-27652-1.html  很详细说明 in ...

  7. [TypeStyle] Reusable styles using TypeStyle mixins

    TypeStyle’s style function allows you to give multiple objects as an argument. This provides a simpl ...

  8. 使用xerces库的一个注意事项

    作者:朱金灿 来源:http://blog.csdn.net/clever101 使用xerces库解析xml文件,结果出现这样一些链接错误: public: static classxercesc_ ...

  9. ios开发网络学习六:设置队列请求与RunLoop

    #import "ViewController.h" @interface ViewController ()<NSURLConnectionDataDelegate> ...

  10. 关于LayoutParams 分类: H1_ANDROID 2013-10-27 20:34 776人阅读 评论(0) 收藏

    每一个布局均有一个叫LayoutParams的内部类,如: LinearLayout.LayoutParams  RelativeLayout.LayoutParams  AbsoluteLayout ...