Java IO_003.Reader与Writer--字符流以及编码对数据的操作(读取与写入)

Java IO之Reader与Writer对象常用操作(包含了编码问题的处理)

涉及到文件(非文件夹)内容的操作,如果是纯文本的情况下,除了要用到File(见之前文章),另外就必须用到字符输入流字符输出流

字符输入流:该流处理时,数据由外部流向程序(内存),一般指代“读取字符”,更清晰点地说:从外部读取字符数据到内存中。

字符输出流:该流处理时,数据由程序(内存)流向外部,一般指代“写入字符”,更清晰点地说:将字符数据从内存写入到外部。

在Java中,可使用:Reader 与 Writer 及其子类。

对字符的操作,采用 Reader与Writer。它们的为声明分别为:

public class FileReader extends InputStreamReader 

public abstract class Writer implements Appendable, Closeable, Flushable

它们都是抽象类,需要由具体子类进行实例化。

Reader主要子类有:

  • BufferedReader:缓存区字符输入流。从缓存区中读取字符数据。类似于BufferedInputStream。
  • CharArrayReader:从字符数组中读取字符数据。类似于ByteArrayInputStream。
  • FileReader:文件字符输入流。从文件中读取字符数据。类似于FileInputStream。
  • FilterReader:过滤器字符输入流。用于装饰。其子类有:PushbackReader, BufferedReader。类似于FilterInputStream。
  • InputStreamReader:字节字符转化流。从字节中读取字符数据。
  • PipedReader:管道字符流。用于从管道中读取字符数据。类似于PipedInputStream。
  • StringReader :字符读取器。从字符中读取数据。

 常用的有FileIReader,我们将以它为例。对比InputStreamReader少了一些从字节中读取数据的类:AudioInputStream,ObjectInputStream,SequenceInputStream,但多了一些从字符中读取数据的类:InputStreamReader, StringReader。

Writer主要子类有:

  • BufferedWriter:缓冲字符输出流。将字符数据写入缓冲区。类似于BufferedOutputStream。
  • CharArrayWriter:字符数组输出流。将字符数据写入字符数组。类似于ByteArrayInputStream。
  • FileWriter:文件输出流。将字符数据写入文件。类似于FileOutputStream。
  • FilterWriter:过滤输出流。用于装饰。类似于FilterOutputStream。
  • OutputStreamWriter:字节字符转化流。
  • PipedWriter:管道字符流。用于将字符数据写入管道。类似于PipedOutputStream。
  • PrintWriter:打印输出流。
  • StringWriter :字符输出流。

 常用的有FileWriter,我们将以它为例。对比OutputStreamWriter少了一些将字节数据写入的类:ObjectOutputStream,但多了一些将字符数据写入的类:OutputStreamWriter,StrngWriter,PrintWriter

Reader与Writer的方法

Reader字符输入流,其主要方法有:

abstract void close() :释放流对象。用于关闭资源。close()后无法进行read及skip等操作。所以一般在流操作结束后进行close()调用。
void mark(int readAheadLimit) :标记流的位置。系统不一定支持。不推荐使用。
boolean markSupported() :检测是否支持流位置标记。(mark方法与reset方法在其支持下才可用。一般能不用则不用此3个方法)
int read() :从流中读取一个字符(一个或两个或三个字节,依据编码决定),如果没有数据,返回-1。
int read(char[] cbuf) :从流中读取字符,并将数据存入到指定的字符数组中,读取的字符数为指定数组的长度。如果没有数据,返回-1。
abstract int read(char[] cbuf, int off, int len) :从流中读取字符,并将数据存入到指定的字符数组中,读取的字符数为len,存入时从数组off开始存。如果没有数据,返回-1。
int read(CharBuffer target) :从流中读取字符,并将数据存入到指定的字符缓冲上,读取的字符数为指定字符缓冲的大小。如果没有数据,返回-1。
boolean ready() :判断流是否可以被读取。
void reset() :从新设置流的开始。系统不一定支持。不推荐使用。
long skip(long n):跳过指定数目字符。可以是正数负数或0(对于文件流来说,该方法虽然已被重载,但不可以用来解决系统不支持reset()问题。负数或0是不支持的,会抛出异常)。

输入字符流Reader用于“读取”,其中最常用的方法是:read(),   read(char[] b),  read(char[] b, int off, int len)

代码:(源代码文件编码为:GBK

 1 import java.io.File;
 2 import java.io.FileReader;
 3 import java.io.IOException;
 4 import java.io.Reader;
 5 
 6 public class Reader001 {
 7     public static void main(String[] args) throws IOException {
 8         Reader r = new FileReader(new File("g:/java2019/file.txt"));//gbk编码的文件,内容为:123abc我爱你
 9         int c = 0;
10         while((c=r.read())!=-1){
11             System.out.println(c+"(0x"+Integer.toHexString(c).toUpperCase()+"):"+(char)c);
12         }
13         r.close();
14     }
15 }

 

输出:

49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):爱
20320(0x4F60):你

改变file.txt的编码为UTF-8,再次运行,输出:

38168(0x9518):锘
65533(0xFFFD):?
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
37812(0x93B4):鎴
25120(0x6220):戠
22477(0x57CD):埍
28003(0x6D63):浣
65533(0xFFFD):?

 

产生乱码,可见,使用FileReader读取数据时,当源文件(程序)与被读取的文件编码不一致的时候,会产生乱码。

将源文件编码也修改为UTF-8,执行输出:

65279(0xFEFF):
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):爱
20320(0x4F60):你

没有产生乱码,但是头部多了一些东西:65279(0xFEFF)。这个是utf-8的BOM

为什么会多这个标记呢?这个与WINDOWS记事本保存时的编码设置有关。

要去除这个BOM标记,可以用IDE或高级文本编码器重新保存一下为UTF-8(去BOM)则可以

如果还是不行,则可以先用WINDOWS记事本保存为GBK(保存为GBK一般会去除BOM),再次在IDE或高级文本编码器中保存为UTF-8。

 

对于字符流,往往涉及到编码问题。所以,在谈及字符操作之前,先来说明一下编码的问题。

WINDOW记事本保存文件时对编码的处理:

1。可以保存为ANSI(系统默认编码,简体中文中一般指GBK,繁体中文中可能是BIG5)或者UTF-8等编码。

2。如果保存为ANSI内置编码,会去除文件中包含的BOM标记。

3。如果保存为UTF-8编码,会自动添加BOM标记。

4。当保存为UTF-8编码时,如果文件内容当中没有任何中文(ANSI)相关的内容出现,经常会自动保存为ANSI编码并去除BOM(不是一定,有些会有些不会。并且这个不仅仅是记事本会产生的问题,而是WINDOWS系统的问题,在WINDOWS下,其它文本编辑工具也会产生这样的问题)。很多时候明明保存为UTF-8保存,却变成了GBK就是这个原因,包括很多用代码生成和写入的纯文本文件。

 

UTF-8对于Java而言最大的问题是:Java编译器不支持带BOM的UTF-8,只支持无BOM的UTF-8源文件的编译。所以如果要采用UTF-8编写源文件的话,必须去除BOM。

去除BOM的方法:

  • 1。用WINDOWS记事本先将编码设置为ANSI,再用支持UTF-8的高级文本编辑器(这种类型的编码器在从ANSI转码为UTF-8时不会主动加BOM,但从有BOM的UTF-8无法转ANSI无法去BOM。比如:Eclipse)保存为UTF-8无BOM编码。
  • 2。直接用支持去BOM的高级文本编码器保存为UTF-8无BOM编码(比如:Editplus,Notepad++)。

 

WINDOWS中查看文件编码的方法:

用记事本打开该文件,然后点击”文件“,再点击”另存为“,这时有”编码“项,当前显示的编码就是该文件的编码。

不要太确信高级文本编辑器(或IDE)显示的编码,因为有时候是错误的。

有时候是因为纯英文无ANSI文字造成了显示UTF-8而真实却是ANSI,有时候是因为UTF-8带BOM设置成ANSI(GBK)无效造成了显示ANSI真实却是UTF-8带BOM。在Eclipse,Notepad++都遇到过这种情形。

另外某些IDE进行编码指定时,并不会改变文件的编码,而是指定读取文件或者运行该文件生成的字节码时的附加编码。所以特别注意:IDE设置的编码真的不一定可靠!

所以要确认文件的编码还是要通过记事本的方法。不要太轻信高级编辑器或IDE。包括上边说到的“去除BOM的方法”还需要最终再用记事本打开的方法再确认一下。

 

随着上边的方案,我们可以做到无BOM的UTF-8。

为了避免BOM或编码问题,编辑器的选择原则是:通用一种编辑器进行文件编辑和编码,不采用另外的编辑器对文件进行修改或二次保存,并且不采用WINDOWS记事本进行编辑(它无法解决带BOM的UTF-8问题),只用WINDOWS记事本来确认编码。

 

java源文件的编码:是指.java文件的编码,在保存的时候可以选择ANSI(系统默认编码,简体中文中一般指GBK,繁体中文中可能是BIG5),UTF-8等。一般都会在ansi和utf-8之间选择一个。由上边的问题可知:如果是UTF-8编码的源文件,必须是无BOM的UTF-8源文件,否则编译时会出现:错误: 需要class, interface或enum

java类文件的编码:是指.class文件的编码。由于.class文件由.java文件产生的,所以源文件什么编码会决定生成的类文件什么编码。但是类文件的编码,并不一定代表程序执行时的最终编码。

程序执行时的最终编码:由Java执行器当中的参数:java -Dfile.encoding=编码   决定。并且,一般情况下,如果没有指定(即省略-Dfile.encoding=编码), 则为类文件的编码。也就是说,默认情况(无指定)下,file.encoding的值由类文件编码决定。

 

在程序中输出 System.getProperty("file.encoding")Charset.defaultCharset()可以获取file.encoding的指定值。这个值或是默认的(类文件编码),或是外部设置的(高级编辑器(或IDE)通过设置编码指定的,或是某些WEB运行环境下配置指定的)所以输出的值如果在命令行或IDE或WEB环境下,都可能会不相同。由于外部可以设置,所以说程序执行时的编码不一定是类文件的编码。另一方面证明了:虽然高级编辑器(或IDE)保存纯英文化的无BOM的UTF-8源文件不成功导致生成了GBK编码的类文件,但执行时仍然是UTF-8形式,就是因为设置UTF-8同时指定了file.encoding。所以在该高级编辑器(或IDE)下进行执行是没问题的,但要保证移植到其它编辑或运行环境下仍然想要正确的结果,那么就要对其它编辑或运行环境设置相同编码约定(有时不需要设定是因为已经一致)。

 

纯英文代码的GBK编码的类文件与UTF编码无BOM的类文件对于执行器执行来说是兼容的,但file.encoding,它会对字符流之类以及对与编码相关的代码产生影响。所以说来说去最重要的还是file.encoding。这个是我们代码无法保证的,因环境变化而变化,我们要确保的是所有环境保持相同的编码,以避免开发与运行的环境不同导致错误的结果。但是另一个问题是,很多时候环境并不是我们能确保的(比如很多第三方环境无法进行配置),这时候涉及到与字符流或编码相关的代码时,我们应当使用支持编码设定的类并配合Charset类进行编码处理,这个是更推荐的办法。还是要强调:不要太依赖于源文件的编码。

 

有了以上编码基础现在开始分析运行FileReader程序读取file.txt的情况。

1。头部输出多余的FEFF :这个属于UTF-8带BOM,先按照“去除BOM的方法”去除掉UTF-8中的BOM。再次输出的时候就不会有多余的BOM标记。

2。IDE与file.txt设置的编码不同的情况下,会出现乱码,设置的编码的情况下,不会出现乱码。

3。构造可以构造GBK编码文件(file-gbk.txt),有BOM的UTF-8编码文件(file-bom.txt),无BOM的UTF-8编码文件(file-nobom.txt)进行测试。以验证上边结论。

(1) 假定IDE设置的源文件编码为:GBK。

分别读取上边3个文件进行输出验证(文件内容都为:123abc我爱你

测试代码:

 1 import java.io.File;
 2 import java.io.FileReader;
 3 import java.io.IOException;
 4 import java.io.Reader;
 5 import java.nio.charset.Charset;
 6 
 7 public class Reader002 {
 8     public static void main(String[] args) throws IOException {
 9         System.out.println("Charset.defaultCharset():" + Charset.defaultCharset());
10         System.out.println("System.getProperty(\"file.encoding\"):" + System.getProperty("file.encoding"));
11 
12         String[] paths = new String[] { 
13                 "g:/java2019/file-gbk.txt", 
14                 "g:/java2019/file-nobom.txt",
15                 "g:/java2019/file-bom.txt" };
16 
17         for (String path : paths) {
18             System.out.println("---------"+path+"----------");
19             Reader r = new FileReader(new File(path));
20             int c = 0;
21             while ((c = r.read()) != -1) {
22                 System.out.println(c + "(0x" + Integer.toHexString(c).toUpperCase() + "):" + (char) c);
23             }
24             r.close();
25         }
26 
27     }
28 }

输出:

Charset.defaultCharset():GBK
System.getProperty("file.encoding"):GBK
---------g:/java2019/file-gbk.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):爱
20320(0x4F60):你
---------g:/java2019/file-nobom.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
37812(0x93B4):鎴
25120(0x6220):戠
22477(0x57CD):埍
28003(0x6D63):浣
65533(0xFFFD):?
---------g:/java2019/file-bom.txt----------
38168(0x9518):锘
65533(0xFFFD):?
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
37812(0x93B4):鎴
25120(0x6220):戠
22477(0x57CD):埍
28003(0x6D63):浣
65533(0xFFFD):?

 

(2) 假定IDE设置的源文件编码为:UTF-8。

测试代码不变,仍然执行,输出:

Charset.defaultCharset():UTF-8
System.getProperty("file.encoding"):UTF-8
---------g:/java2019/file-gbk.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
65533(0xFFFD):�
1200(0x4B0):Ұ
65533(0xFFFD):�
65533(0xFFFD):�
65533(0xFFFD):�
---------g:/java2019/file-nobom.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):爱
20320(0x4F60):你
---------g:/java2019/file-bom.txt----------
65279(0xFEFF):
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):爱
20320(0x4F60):你

 

另外,通过命令行也可以分别进行测试:
java Reader002  回车输出:

Charset.defaultCharset():GBK
System.getProperty("file.encoding"):GBK
---------g:/java2019/file-gbk.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):爱
20320(0x4F60):你
---------g:/java2019/file-nobom.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
37812(0x93B4):鎴
25120(0x6220):戠
22477(0x57CD):埍
28003(0x6D63):浣
65533(0xFFFD):?
---------g:/java2019/file-bom.txt----------
38168(0x9518):锘
65533(0xFFFD):?
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
37812(0x93B4):鎴
25120(0x6220):戠
22477(0x57CD):埍
28003(0x6D63):浣
65533(0xFFFD):?

java -Dfile.encoding=utf-8 Reader002 回车输出:

Charset.defaultCharset():UTF-8
System.getProperty("file.encoding"):utf-8
---------g:/java2019/file-gbk.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
65533(0xFFFD):?
1200(0x4B0):?
65533(0xFFFD):?
65533(0xFFFD):?
65533(0xFFFD):?
---------g:/java2019/file-nobom.txt----------
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):爱
20320(0x4F60):你
---------g:/java2019/file-bom.txt----------
65279(0xFEFF):?
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):爱
20320(0x4F60):你

以上测试验证了上边的结论:

(1).执行程序的编码/Charset.defaultCharset()/System.getProperty("file.encoding") 是由运行环境的-Dfile-encoding=XXX决定的。没有指定时,-Dfile-encoding默认等于类文件的编码(这时才由类文件编码决定)

(2).执行程序的编码与要处理的文本文件的编码不一致时,在没有任何代码相关的编码处理时(上边的测试程序没有应用编码相关的代码处理,而FileReader默认使用Charset.defaultCharset()),会出现乱码。另外,程序中使用System.setProperty("file.encoding","utf-8");是没有作用的。

 

如果要处理的文件资源编码不可变(比如现在要读取file-nobom.txt这个UTF-8无BOM文件)同时file.encoding也无法设置(假定当前为GBK)。那么显示编码是冲突了,使用FileReader使用的是默认的无法改变的编码(与file.encoding相同:GBK),如果要正常输出,这是不可能的,不过Reader的子类:InputStreamReader可以处理编码冲突的情况,它通过InputStream并指定编码,可以用来处理字符并解决编码问题。测试代码:

 1 import java.io.File;
 2 import java.io.FileInputStream;
 3 import java.io.IOException;
 4 import java.io.InputStreamReader;
 5 import java.io.Reader;
 6 import java.nio.charset.Charset;
 7 
 8 public class Reader003 {
 9     public static void main(String[] args) throws IOException {
10         System.out.println("Charset.defaultCharset():"+Charset.defaultCharset());
11         System.out.println("System.getProperty(\"file.encoding\"):"+System.getProperty("file.encoding"));
12         File file = new File("g:/java2019/file-nobom.txt");
13         Reader r = new InputStreamReader(new FileInputStream(file),"utf-8");//内容为:123abc我爱你
14         int c = 0;
15         while((c=r.read())!=-1){
16             System.out.println(c+"(0x"+Integer.toHexString(c).toUpperCase()+"):"+(char)c);
17         }
18         r.close();
19     }
20 }

输出:

Charset.defaultCharset():GBK
System.getProperty("file.encoding"):GBK
49(0x31):1
50(0x32):2
51(0x33):3
97(0x61):a
98(0x62):b
99(0x63):c
25105(0x6211):我
29233(0x7231):爱
20320(0x4F60):你

说明:通过InputStreamReader,可以在编码不匹配的情况下解决冲突问题,解决了FileReader默认编码的问题。在不能进行环境设置的情况下特别有用!!!

InputStreamReader的类声明如下:

public class InputStreamReader extends Reader

InputStreamReader主要的构造方法有:

public InputStreamReader(InputStream in):采用默认编码利用InputStream进行构造。相同于:InputStreamReader(InputStream in, System.getProperty("file.encoding")) 或 InputStreamReader(InputStream in, Charset.defaultCharset())

public InputStreamReader(InputStream in, String charsetName):通过字符串指明编码利用InputStream进行构造。常用。

public InputStreamReader(InputStream in, Charset cs):通过Charset对象指明编码利用InputStream进行构造。

public InputStreamReader(InputStream in, CharsetDecoder dec):较为复杂,少用。

 

现在继续说Reader的读取操作,可以更高效的处理的方法,是采用:read(char[] c, int off, int len) 来减少读取次数,毕竟每次读取会阻塞,减少了读取次数,效率就提高了。

一次性读取不同编码的文件内容,代码 :

 1 import java.io.File;
 2 import java.io.FileInputStream;
 3 import java.io.IOException;
 4 import java.io.InputStreamReader;
 5 import java.io.Reader;
 6 
 7 public class Reader004 {
 8     public static void main(String[] args) throws IOException {
 9         File file = new File("g:/java2019/file-nobom.txt");
10         Reader r = new InputStreamReader(new FileInputStream(file),"utf-8");//内容为:123abc我爱你
11         
12         char[] c = new char[(int)file.length()];
13         while((r.read(c))!=-1){
14             System.out.println(c);
15         }
16         r.close();
17     }
18 }

输出:123abc我爱你

 

非一次性读取代码:

 1 import java.io.File;
 2 import java.io.FileInputStream;
 3 import java.io.IOException;
 4 import java.io.InputStreamReader;
 5 import java.io.Reader;
 6 import java.util.Arrays;
 7 
 8 public class Reader005 {
 9     public static void main(String[] args) throws IOException {
10         File file = new File("g:/java2019/file-nobom.txt");
11         Reader r = new InputStreamReader(new FileInputStream(file),"utf-8");//内容为:123abc我爱你
12         
13         int len=0;
14         char[] c = new char[3];
15         while((len=r.read(c))!=-1){
16                 System.out.println(Arrays.copyOfRange(c, 0, len));
17         }
18         r.close();
19     }
20 }

输出:

123
abc
我爱你

 

Writer字符输出流,其主要方法有:

Writer append(char c) :以追加的方式写入一个字符。可链式调用。
Writer append(CharSequence csq) :以追加的方式写入一个字符序列。可链式调用。
Writer append(CharSequence csq, int start, int end) :以追加的方式写入一个指定开始与结束位置的字符序列。可链式调用。
abstract void close() :释放流对象。用于关闭资源。close()后无法进行read及skip等操作。所以一般在流操作结束后进行close()调用。
abstract void flush() :刷新输出流。特别在使用缓冲区时,如果在进行write和append方法当缓冲区超过输出数据量时,会自动刷新并写入,如果没有超过则不会进行刷新与写入。如果没有close()方法时末端未超过的可能不会进行写入。
                                    不过如果采用JDK1.7的try-resource异常处理,会进行自动close()关闭,倒可以放心。
void write(char[] cbuf) :写入一个字符数组数据。
abstract void write(char[] cbuf, int off, int len) :写入一个指定开始与结束的字符数组数据。
void write(int c) :写入一个字符数据。
void write(String str) :写入一个字符串的数据。
void write(String str, int off, int len):写入一个指定开始与结束的字符串的数据。

 

输出字符流Writer用于“写入”,其中最常用的方法是:write(int c),   write(char[] b),  write(char[] b, int off, int len),  writer(String str) ,   Write(String str, int off, int len)

测试代码:

 1 import java.io.FileWriter;
 2 import java.io.IOException;
 3 import java.io.Writer;
 4 import java.nio.charset.Charset;
 5 
 6 public class Write001 {
 7     public static void main(String[] args) throws IOException {
 8         System.out.println(Charset.defaultCharset());
 9         Writer w = new FileWriter("g:/java2019/file-001.txt");
10         w.write("我爱你");
11         w.close();
12     }
13 }

输出:UTF-8

生成或改变的文件格式为:UTF-8

内容为:我爱你

说明:FileWriter按照默认的file.encoding生成了utf-8格式的文件,并且写入了相应的字符串“我爱你”

 

改下程序:将写入的字符串“我爱你”改成“123abc"

 1 import java.io.FileWriter;
 2 import java.io.IOException;
 3 import java.io.Writer;
 4 import java.nio.charset.Charset;
 5 
 6 public class Write001 {
 7     public static void main(String[] args) throws IOException {
 8         System.out.println(Charset.defaultCharset());
 9         Writer w = new FileWriter("g:/java2019/file-001.txt");
10         w.write("123abc");
11         w.close();
12     }
13 }

输出:UTF-8

文件格式变为:ANSI(GBK)

文件内容:123abc

可见:纯英文下的UTF-8在生成的时候,会自动变成GBK。

现在可以进行纯文本文件的复制,代码为:

 1 import java.io.FileReader;
 2 import java.io.FileWriter;
 3 import java.io.IOException;
 4 import java.io.Reader;
 5 import java.io.Writer;
 6 import java.nio.charset.Charset;
 7 
 8 public class Write002 {
 9     public static void main(String[] args) {
10         System.out.println(Charset.defaultCharset());
11         try (Reader r = new FileReader("g:/java2019/file.txt");
12                 Writer w = new FileWriter("g:/java2019/file-001.txt");) {
13             int len = 0;
14             char[] c = new char[1024];
15             while ((len = r.read(c)) != -1) {
16                 w.write(c, 0, len);
17             }
18         } catch (IOException e) {
19             e.printStackTrace();
20         }
21     }
22 }

输出:utf-8

生成的UTF-8格式文件file-001.txt内容为:123abc�Ұ���

显然乱码了,原因在于读取的资源文件编码为GBK与file-encoding冲突。解决办法上边已经使用过多次。要么将file.txt编码更换为utf-8,要么采用读取转换流。这里用后者,代码更改为:

 1 import java.io.FileInputStream;
 2 import java.io.FileWriter;
 3 import java.io.IOException;
 4 import java.io.InputStreamReader;
 5 import java.io.Writer;
 6 import java.nio.charset.Charset;
 7 
 8 public class Write003 {
 9     public static void main(String[] args) {
10         System.out.println(Charset.defaultCharset());
11         try (InputStreamReader r = new InputStreamReader(new FileInputStream("g:/java2019/file.txt"),Charset.forName("GBK"));
12                 Writer w = new FileWriter("g:/java2019/file-001.txt");) {
13             int len = 0;
14             char[] c = new char[1024];
15             while ((len = r.read(c)) != -1) {
16                 w.write(c, 0, len);
17             }
18         } catch (IOException e) {
19             e.printStackTrace();
20         }
21     }
22 }

 

现在不会出现乱码了!!

另外,Java还为高效的操作字符流提供了缓冲字符输入流BufferedReader与缓冲字符输出流BufferedWriter,操作代码:

 1 import java.io.BufferedReader;
 2 import java.io.BufferedWriter;
 3 import java.io.FileInputStream;
 4 import java.io.FileWriter;
 5 import java.io.IOException;
 6 import java.io.InputStreamReader;
 7 import java.nio.charset.Charset;
 8 
 9 public class Write004 {
10     public static void main(String[] args) {
11         System.out.println(Charset.defaultCharset());
12         try (BufferedReader r = new BufferedReader(
13                 new InputStreamReader(new FileInputStream("g:/java2019/file.txt"), Charset.forName("GBK")));
14                 BufferedWriter w = new BufferedWriter(new FileWriter("g:/java2019/file-001.txt"));) {
15             int len = 0;
16             char[] c = new char[1024];
17             while ((len = r.read(c)) != -1) {
18                 w.write(c, 0, len);
19             }
20         } catch (IOException e) {
21             e.printStackTrace();
22         }
23     }
24 }

可见,完全可以兼容我们自己写的复制代码,只需要替换特定的输入流与输出流的构造部分,其它部分可以不需要变化。另外,缓冲的字符流还提供了比较好用的一行一行的读取与写入的方法,以及写入换行的方法,见代码:

 1 import java.io.BufferedReader;
 2 import java.io.BufferedWriter;
 3 import java.io.FileInputStream;
 4 import java.io.FileWriter;
 5 import java.io.IOException;
 6 import java.io.InputStreamReader;
 7 import java.nio.charset.Charset;
 8 
 9 public class Write005 {
10     public static void main(String[] args) {
11         System.out.println(Charset.defaultCharset());
12         try (BufferedReader r = new BufferedReader(
13                 new InputStreamReader(new FileInputStream("g:/java2019/file.txt"), Charset.forName("GBK")));
14                 BufferedWriter w = new BufferedWriter(new FileWriter("g:/java2019/file-001.txt"));) {
15             String s = null;
16             while ((s = r.readLine())!=null) {
17                 w.write(s);
18                 w.newLine();
19             }
20         } catch (IOException e) {
21             e.printStackTrace();
22         }
23     }
24 }

另外还可以使用打印流PrintWriter进行文件复制:

 1 import java.io.BufferedReader;
 2 import java.io.FileInputStream;
 3 import java.io.FileWriter;
 4 import java.io.IOException;
 5 import java.io.InputStreamReader;
 6 import java.io.PrintWriter;
 7 import java.nio.charset.Charset;
 8 
 9 public class Write006 {
10     public static void main(String[] args) {
11         System.out.println(Charset.defaultCharset());
12         try (BufferedReader r = new BufferedReader(
13                 new InputStreamReader(new FileInputStream("g:/java2019/file.txt"), Charset.forName("GBK")));
14                 PrintWriter w = new PrintWriter(new FileWriter("g:/java2019/file-001.txt"));) {
15             String s = null;
16             while ((s = r.readLine())!=null) {
17                 w.println(s);
18             }
19         } catch (IOException e) {
20             e.printStackTrace();
21         }
22     }
23 }

说明:打印流有println可以输出并换行(内部调用了newLine())。另外,打印流的write(int c)与print(int c)区别:前者会转化为字符进行写入,后者直接原样写入。

 

其它的字符处理类:暂不介绍。

 

总结:

  • FileReader与FileWriter可以以字符流形式处理文件,进行读取或写入操作。
  • 要进行高效的字符流处理,可以使用内置的BufferedReader及BufferedWriter缓冲字符流,它可以处理各种Reader及Writer(不仅仅是FileReader及FileWriter)
  • 使用read(char[] b, int off, int len)及write(char[], int off, int len) 可以更高效的进行字符复制(可以替代缓冲字符流)
  • 对于文件编码不一致时,可以采用转换输入流InputStreamReader或转换输出流OutputStreamWriter进行转码操作

 

上一篇:每周一道算法题003:翻牌


下一篇:《算法笔记》学习记录003