您的位置:首页 > 编程语言 > Java开发

用Java从UTF-8文本文件中读取内容

2016-09-25 16:52 393 查看

整体思路:

(1)读取源文件内容(UTF-8编码无BOM), 通过FileReader字符流完成;

(2)向目标文件写内容,通过FileWriter字符流完成;

(3)向控制台输出内容;

最后的结果是,目标文件为UTF-8编码格式,一切正常;控制台得到的输出,中文字符为乱码,英文字母,标点符号一切正常。

解码过程

我查阅了Java API文档,官方在线文档在这里。结合之前理解的Java输入流与输出流的知识,找到了使用另一种方法,在不更改控制台编码集的情况下,得到正常输出。

Java的FileReader字符输入流,是Java中读取文本文件的重要类,为了理解文本文件读取具体过程,查阅API doc,发现FileReader类有如下继承关系

java.io.Reader
java.io.InputStreamReader
java.io.FileReader


InputStreamReader是一个“转换流”,将字节输入流转换成字符输入流,FileReader继承了此类。说明FileReader类在读取文本文件时,首先将文件作为字节流读入,然后使用InputStreamReader的类似功能,将字节流转换为字符流。

这里需要解释下,所有内容在计算机中的存储形式,都是二进制形式,原始的二进制文件的基本读取单位是字节(byte),而对于文本文件,字符使用一个整数来表示的,unicode中,把这个表示字符的整数称为“code point”。encode(编码)和decode(解码)的过程,可用下面的方式理解:

字符.encode()------>二进制字节

二进制字节.decode()--------->字符


InputStreamReader转换流所实现的功能,就是一个decode的过程。

API doc 中,FileReader类的描述中,有这样一句:

“The constructors of this class assume that the default character encoding and the default byte-buffer size are appropriate”


意即“该类构造器假定默认字符编码和默认缓冲字节大小是合适的”。说明FileReader在将字节与字符进行encode(编码)和decode(解码)的过程中,是假定了一个默认的,“合适”的字符集。

而FileReader的父类InputStreamReader,是这样描述的

“It reads bytes and decodes them into characters using a specified charset. The charset that it uses may be specified by name or may be given explicitly, or the platform's default charset may be accepted.”


意即“InputStreamReader读取字节,并将它们解码为字符,解码过程中使用指定的字符集(charset)……如果没有显示指定字符接,使用平台的默认字符集。”Windows平台的默认字符集是GBK(ANSI标准),Linux平台的默认字符集是“UTF-8”。而如果一个UTF-8字节编码的文本文件,使用GBK字符集解码,那当然就会产生乱码,反过来也一样。而英文字母、符号永远不会出现乱码的原因,是ASCII字符表是UTF-8的一个子集,也是GBK的一个子集,UTF-8中英文字母的字节码与GBK字符集中英文字母的字节码完全相同。

因此,另外一种解决方式,是使用InputStreamReader转换流,将一个字节流转换为字符流,并在转换过程中,指定使用“UTF-8”字符集,对字节进行解码。核心代码如下:

InputStreamReader ipr = new InputStreamReader(new FileInputStream("HelloTest.java"),Charset.forName("UTF-8");
//使用BufferedReader进行封装,是为了调用readLine()函数,方便操作
BufferedReader br = new BufferedReader(ipr);
//其余操作......


等等,那编码过程呢?

在指定解码字符集之后,就能得到正常输出。但是有另外一个问题,第一种解决办法,是更改了MyEcplise控制台的默认编码,才得到正常输出。同时由于Windows的cmd窗口无法更改默认编码,只能使用GBK,依然是乱码。为什么在Java代码中,更改了InputStreamReader的一个参数,在GBK编码的控制台,依然能正常输出“UTF-8”字符呢?

Java的标准输出流System.out,将字符内容输出到控制台。查阅Java Doc文档,可以看到,out其实是System类中的一个静态成员。而out的类型为PrintStream。因此,关键是要理解PrintStream类中的方法成员println()/print()的执行原理。在Java Doc文档中,有描述“All characters printed by a PrintStream are converted into bytes using the platform’s default character encoding. ”意即“被PrintStream打印(print)的所有字符,都使用平台默认的字符编码集,转换为字节。”。也就是说,System.out.print()/pinrtln()方法,使用平台默认字符集,将字符串encode(编码)为字节,然后传给了控制台,控制台接收到字节后,当然会使用平台默认的字符集,将字节decode(解码)为字符串。

即Java会将程序中的字符,先编码,再放入标准输出流System.out,编码字符集为平台默认字符集。因此,只要在读取输入流的过程中,将字节正确地解码为字符,后续的标准输出其实是与原文件编码格式无关的。

但是之前说过,计算机中所有的数据都是二进制字节,文本文件的存储方式也是二进制字节;那么,在Java程序中,字符是以什么形式保存的?再次查阅Java Doc,发现Java中char数据类型用如前所述的 UNICODE 表示,UNICODE标准给每个字符分配一个唯一的整数,使用16进制编码方式表示,一共可以表示65536个字符,可以理解为无符号的16位整数。因此Java程序中,char类型是可以和Int类型相互转换的。

Sep 25th, 2016
Shenzhen
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: