使用FileReader采用的默认编码

目录
  • FileReader采用的默认编码
    • 事情的真相是什么呢?
  • FileReader的编码问题
    • 为什么结果中还是有部分乱码呢?

FileReader采用的默认编码

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

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

代码如下:

package day170903;
import java.io.*;
public class TestDecoder {
	public static void main(String[] args) {
		FileReader fr = null;
		try {
			fr = new FileReader("G:/io/hello.txt");
			int len = 0;
			while((len=fr.read())!=-1) {
				System.out.println((char)len);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if(fr!=null) {
					fr.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

事情的真相是什么呢?

编码一般是在构造方法处指定的,于是查看一下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中的那个构造方法的具体内容。

    public FileReader(String fileName) throws FileNotFoundException {
        super(new FileInputStream(fileName));
    }

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

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

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

    protected Reader(Object lock) {
        if (lock == null) {
            throw new NullPointerException();
        }
        this.lock = lock;
    }

在这里,它只是把得到的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方法,对应的代码如下:

public static StreamDecoder forInputStreamReader(InputStream in,
                                                 Object lock,
                                                 String charsetName)
    throws UnsupportedEncodingException
{
    String csn = charsetName;
    if (csn == null)
        csn = Charset.defaultCharset().name();
    try {
        if (Charset.isSupported(csn))
            return new StreamDecoder(in, lock, Charset.forName(csn));
    } catch (IllegalCharsetNameException x) { }
    throw new UnsupportedEncodingException (csn);
}

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

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

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

public static Charset defaultCharset() {
    if (defaultCharset == null) {
        synchronized (Charset.class) {
            String csn = AccessController.doPrivileged(
                new GetPropertyAction("file.encoding"));
            Charset cs = lookup(csn);
            if (cs != null)
                defaultCharset = cs;
            else
                defaultCharset = forName("UTF-8");
        }
    }
    return defaultCharset;
}

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

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

这代码看起来很费劲,而且接着又要看其它代码。最终结果是这个所谓的默认字符编码,其实就是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。

package day170903;
import java.io.*;
public class TestDefaultCharEncoding {
	public static void main(String[] args) {
		InputStreamReader isr = null;
		OutputStreamWriter osw = null;
		try {
			osw = new OutputStreamWriter(new FileOutputStream("G:/io/ISO-8859-1.txt"),"ISO-8859-1");
			isr = new InputStreamReader(new FileInputStream("G:/io/ISO-8859-1.txt"));
			char[] chars = "Est-ce possible que tu sois en train de penser à moi lorsque tu me manques?".toCharArray();
			osw.write(chars);
			osw.flush();
			int len = 0;
			while((len=isr.read())!=-1) {
				System.out.print((char)len);
			}
		} catch (UnsupportedEncodingException | FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if(isr!=null) {
					isr.close();
				}
				if(osw!=null) {
					osw.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

输出结果是:

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

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

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

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

FileReader的编码问题

有一个UTF-8编码的文本文件,用FileReader读取到一个字符串,然后转换字符集:str=new String(str.getBytes(),"UTF-8");结果大部分中文显示正常,但最后仍有部分汉字显示为问号!

public static List<String> getLines( String fileName )
{
    List<String> lines = new ArrayList<String>();
    try
    {
        BufferedReader br = new BufferedReader(new FileReader(fileName));
        String line = null;
        while( ( line = br.readLine() ) != null )
            lines.add(new String(line.getBytes("GBK"), "UTF-8"));
        br.close();
    }
    catch( FileNotFoundException e )
    {
    }
    catch( IOException e )
    {
    }
    return lines;
}  

文件读入时是按OS的默认字符集即GBK解码的,我先用默认字符集GBK编码str.getBytes(“GBK”),此时应该还原为文件中的字节序列了,然后再按UTF-8解码,生成的字符串按理说应该就应该是正确的。

为什么结果中还是有部分乱码呢?

问题出在FileReader读取文件的过程中,FileReader继承了InputStreamReader,但并没有实现父类中带字符集参数的构造函数,所以FileReader只能按系统默认的字符集来解码,然后在UTF-8 -> GBK -> UTF-8的过程中编码出现损失,造成结果不能还原最初的字符。

原因明确了,用InputStreamReader代替FileReader,InputStreamReader isr=new InputStreamReader(new FileInputStream(fileName),"UTF-8");这样读取文件就会直接用UTF-8解码,不用再做编码转换。

public static List<String> getLines( String fileName )
{
    List<String> lines = new ArrayList<String>();
    try
    {
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(fileName), "UTF-8"));
        String line = null;
        while( ( line = br.readLine() ) != null )
            lines.add(line);
        br.close();
    }
    catch( FileNotFoundException e )
    {
    }
    catch( IOException e )
    {
    }
    return lines;
}  

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • InputStreamReader 和FileReader的区别及InputStream和Reader的区别

    首先给大家介绍InputStreamReader 和FileReader的区别,具体内容如下所示: InputStreamReader 和 BufferedReader .其中最重要的类是 InputStreamReader ,它是字节转换为字符的桥梁. 你可以在构造器重指定编码的方式,如果不指定的话将采用底层操作系统的默认编码方式,例如 GBK 等. FileReader 与 InputStreamReader 涉及编码转换 ( 指定编码方式或者采用 os 默认编码 ) ,可能在不同的平台上出

  • Java文件字符输入流FileReader读取txt文件乱码的解决

    目录 Java文件字符输入流FileReader读取txt文件乱码 先上代码 控制台输出结果如下 原因是 运行之后的结果为 字符流读取UTF-8和写出txt文件乱码问题 话不多说,直接上图 解决 Java文件字符输入流FileReader读取txt文件乱码 先上代码 public class FileInAndOut { public static void main(String[] args) { //定义指定磁盘的文件的File对象 File file = new File("E:/大三下

  • Java 中的FileReader和FileWriter源码分析_动力节点Java学院整理

    FileReader和FileWriter源码分析 1. FileReader 源码(基于jdk1.7.40) package java.io; public class FileReader extends InputStreamReader { public FileReader(String fileName) throws FileNotFoundException { super(new FileInputStream(fil java io系列21之 InputStreamReade

  • 使用FileReader采用的默认编码

    目录 FileReader采用的默认编码 事情的真相是什么呢? FileReader的编码问题 为什么结果中还是有部分乱码呢? FileReader采用的默认编码 很久以前听教学视频,里面讲到Java采用的默认编码是ISO-8859-1,一直记着. 但是最近重新看IO流的时候,惊讶地发现,在不指定字符编码的情况下,FileReader居然可以读取内容为中文的文本文件.要知道ISO-8859-1可是西欧字符集,怎么能包含中文呢?于是百度了一下关键词"IOS-8859-1显示中文",结果很

  • MsSQL数据导入到Mongo的默认编码问题(正确导入Mongo的方法)

    先说下我操作的步骤.1.从sql2008的management studio查询5万条数据,使用右键导出为csv2.由于默认导出没有带列名,手工编辑后增加了列名3.使用mongoimport导入数据,错误,提示invalid utf8 character 检查了一下,是因为默认导出为csv的时候,不是utf8的格式而是系统的默认编码,采用openoffice编辑另存为utf8格式就可以解决编码问题,但是发现使用openoffice后列名和数据不匹配,列名很多都叠在一起了,虽然数据是能导入进去了,

  • 借助FileReader实现将文件编码为Base64后通过AJAX上传

    使用AJAX是无法直接上传文件的,一般都是新建个iframe在它里面完成表单提交的过程以达到异步上传文件的效果. 如此做可以达到比较好的浏览器兼容性,不过代码量会比较大,即使是使用了文件上传插件,例如plupload. 如何能达到灵活的程度呢,能像普通的AJAX提交表单数据那样将文件看成是普通表单参数来对待就好了. 灵光一闪,利用javascript的FileReader对象将文件编码成base64再传服务器不就行了么~ 开始动手,丰衣足食. 前端对文件进行base64编码并通过ajax向服务器

  • python实现unicode转中文及转换默认编码的方法

    本文实例讲述了python实现unicode转中文及转换默认编码的方法.分享给大家供大家参考,具体如下: 一.在爬虫抓取网页信息时常需要将类似"\u4eba\u751f\u82e6\u77ed\uff0cpy\u662f\u5cb8"转换为中文,实际上这是unicode的中文编码.可用以下方法转换: 1. >>> s = u'\u4eba\u751f\u82e6\u77ed\uff0cpy\u662f\u5cb8' >>> print s 人生苦短,

  • 修改mysql5.5默认编码(图文步骤修改为utf-8编码)

    mysql数据库的默认编码并不是utf-8. 安装mysql后,启动服务并登陆,使用show variables命令可查看mysql数据库的默认编码: 由上图可见database和server的字符集使用了latin1编码方式,不支持中文,即存储中文时会出现乱码.以下是命令行修改为utf-8编码的过程,以支持中文. (1)关闭mysql服务 复制代码 代码如下: service mysql stop (2)修改 /etc/mysql/my.cnf  (默认的安装路径) 复制代码 代码如下: vi

  • Python设置默认编码为utf8的方法

    本文实例讲述了Python设置默认编码为utf8的方法.分享给大家供大家参考,具体如下: 这是Python的编码问题,设置python的默认编码为utf8 python安装目录:/etc/python2.x/sitecustomize.py import sys reload(sys) sys.setdefaultencoding('utf-8') try: import apport_python_hook except ImportError: pass else: apport_pytho

  • 关于Apache默认编码错误 导致网站乱码的解决方案

    最近经常有同学在使用LAMP/WAMP时,遇到这样的编码错误问题: A网站程序编码UTF-8编码安装成功,运行成功. B网站程序编gb2312也要安装在同一服务器上. 这样就出现问题了,Apache默认编码UTF-8在解析A网站的时候没有任何问题,当运行B网站时出现的"蝌蚪文"乱码问题. 单纯的修改Apache默认编码为gb2312这样就导致A网站出现"蝌蚪文". 问题分析: 如果你在网上搜索 "apache配置",搜到的页面大多都会建议你在ht

  • Linux使用MySQL忘记root密码及修改MySQL默认编码

    概述: 本文不再对MySQL的语法进行讲解和说明,想了解或熟悉的朋友请自行百度或Google学习.本文主要是针对MySQL除语法之外的总结,希望能够也能帮助到你. 1.CentOS6.x下MySQL忘记root密码解决方法 Ⅰ. 修改MySQL的登录设置 # vim /etc/my.cnf 在[mysqld]段中加上一句:skip-grant-tables Ⅱ. 重启服务 # service mysqld restart Ⅲ. 登录Mysql,修改密码信息 # mysql mysql> USE

  • Linux中修改mysql默认编码的方法步骤

    在开发过程中,如果还原MySQL数据库后,数据库数据出现乱码,可以通过修改数据库默认编码来解决. 以下以把MySQL默认编码修改为UTF-8作为例子演示修改流程: 1.先查看mysql的信息 # 查看数据库安装位置 whereis mysql # 登录数据库 mysql -u root -p 按提示输入密码 # 查看mysql状态 mysql>status 2.修改my.cnf 文件 目录为/etc/my.cnf 如果系统中没有my.cnf文件.则需要创建此文件,具体步骤请看3,如果存在,直接跳

  • mysql默认编码为UTF-8 通过修改my.ini实现方法

    mysql汉字乱码的原因 mysql默认的编码是Latin1是I-8859-1的别名,但Latin1是不支持汉字的,所以要将其改为UTF-8或GBK 1.关闭mysql服务器,这个很重要. 2.通过my.ini设置mysql数据库的编码 在mysql数据库的安装根目录下找到my.ini,例:C:\Program Files\MySQL\MySQL Server 5.5 将其复制到桌面,双击打开, 搜索"default-character-set"将其改为utf8, 搜索"ch

随机推荐