zoukankan      html  css  js  c++  java
  • 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决定的(通常可以由外部环境指定。外部环境一般指:命令行参数或IDE编码设置或WEB环境文件配置)。如果没有指定时,-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。

    如果需要在纯英文下也能生成UTF-8,则可以采取添加BOM头,一旦有BOM头并指明UTF-8,则必须是UTF-8格式。代码如:

     1 import java.io.File;
     2 import java.io.FileOutputStream;
     3 import java.io.IOException;
     4 import java.io.OutputStreamWriter;
     5 import java.nio.charset.Charset;
     6 
     7 public class Write001_2 {
     8     public static void main(String[] args) throws IOException {
     9         System.out.println(Charset.defaultCharset());
    10         File file = new File("g:/java2019/file-001.txt");
    11         FileOutputStream fos = new FileOutputStream(file);
    12         //fos.write BOM header
    13         fos.write(0xEF);
    14         fos.write(0xBB);
    15         fos.write(0xBF);
    16         fos.close();
    17         fos = new FileOutputStream(file,true);
    18         OutputStreamWriter w = new OutputStreamWriter(fos,"utf-8");
    19         w.write("123abc");
    20         w.close();
    21     }
    22 }

    一旦头部添加了BOM标记,即使new OutputStreamWriter(fos,"utf-8") 设置编码为gbk,仍然会生成UTF-8(带BOM)。不过生成的带BOM的UTF-8文件如果要程序中要再次进行读取操作的话,就会出现头部有BOM的问题。除非一定要生成UTF-8编码文本或者生成之后不进行后续的被代码程序读取,否则指定UTF-8而被生成GBK格式的文件也是无所谓的(后续遇到程序要处理,也可以配合转换流进行操作)

    不考虑复制后是否UTF-8格式,现在进行纯文本文件的复制,代码为:

     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进行转码操作
  • 相关阅读:
    You are not late! You are not early!
    在同一个服务器(同一个IP)为不同域名绑定的免费SSL证书
    Vue.js Is Good, but Is It Better Than Angular or React?
    It was not possible to find any compatible framework version
    VS增加插件 Supercharger破解教程
    Git使用ssh key
    Disconnected: No supported authentication methods available (server sent: publickey)
    VS 2013打开.edmx文件时报类型转换异常
    asp.net MVC4 框架揭秘 读书笔记系列3
    asp.net MVC4 框架揭秘 读书笔记系列2
  • 原文地址:https://www.cnblogs.com/dreamyoung/p/11131987.html
Copyright © 2011-2022 走看看