java中级-7-IO流知识点串讲(1)-IO流概念及字符流Writer、Reader


------- android培训java培训、期待与您交流! ----------



I/O  Input/Output

        Java对数据的操作是通过流的方式来完成的,我们将它操纵数据时,对应流的情况做出了划分。按操作的数据类型来分,分为两种:字节流、字符流;按流的方向来分,也分为两种:输入流、输出流。

        字符流的产生,是为了解决编码转换的问题,主要传输的是文字。这样传输的文字,我们能够指定解码方式,例如:指定使用UNI-8,或者GBK-2312。通用的是字节流,而字符流属于自节流的分支。

        这样,我们就产生了对应I/O常用的4个基类:

        字节流抽象基类:InputStream、OutputStream

        字符流抽象基类:Reader、Writer

        Input 对应读取;Output 对应写入

        此四个基类派生出来的子类,都是以父类名称作为后缀。例如:FileInputStream、FileReader。为了说明I/O的使用,我们首先来看字节流的抽象基类Writer。

  

字符流Writer

        它主要实现对文字的写入操作,我们首先以其子类OutputStreamWriter的子类FileWriter来说明它的作用,并以此类推出该类的特点。

        FileWriter主要实现对文件的写操作。该类的所有方法除构造函数外,都是继承自其父类。常用的方法有write,flush,close。这三个方法分别实现对文件的写入缓冲操作,刷新缓冲操作(刷新缓存同时,将内容写入对应文件当中),关闭流。具体用法如下例:

import java.io.*;
class  FileWriterTest
{
       public static void main(String[] args)
       {
              FileWriter File_w = newFileWriter("test.txt"); //创建了一个名为test的文本
              File_w.write("123");   //将123添加到输入流
              File_w.flush();   //刷新输入流,将123录入文件
 
              File_w.write("456");      //将456添加进输入流
              File_w.close();   //关闭输入流,在关闭前,将456刷新入test文件中
 
       }
}

        由例子,我们不难看出,在使用FileWriter类的方法对文件操作前,我们首先得有对应的文件,这时,我们可以通过对应的方法来创建一个相应的文件,或者,把想要操纵的文件,它的文件名通过构造函数传给对应FileWriter类对象。只有这样,我们才能又可以操作的目的文件。

        在对文件进行写操作的时候,我们需要注意的一点是write、flush、close三者的顺序。这三者的调用顺序应该是这样的:


图2.Writer字符流写操作顺序图

 

        需要注意的一点是flush只是刷新流,流本身还存在,即可以继续使用;close则是关闭流。如果选择关闭流,则调用close方法的时候,close本身在关闭之前,就会执行一次flush,这次刷新操作是封装在close函数内的。所以,如果我们只想进行一次输入,那么我们就可以把flush操作省略掉,直接初始化FileWriter对象,调用write和close即可。关闭之后的流,是无法写入数据的,否则会出现IOException异常。

        对于IOException异常的处理,采取抛的策略,是不太合适的,容易导致程序不健壮。而从文件调取(或创建)开始,知道流关闭close结束,这过程中的每一步都有可能出现IO异常,那么,我们对流的IO异常处理就得用下面的模版中的分部处理思想来考虑了,来看: 

import java.io.*;
class  FileWriter_IOExecptionTest
{
       public static void main(String[] args)
       {
              FileWriter File_w = null; //在外部建立声明,以便于close的调用
              try
              {
                     //try内部建立的变量是属于该程序快的局部变量,因此,如果需要,需建立外部声明
                     File_w = newFileWriter("test.txt");
                     File_w.write("123");  
              }
              catch (IOException e)
              {
                     System.out.println(e.toString());
              }
              finally
              {
                     if(File_w!=null)  //如果文件对象创建或选取的操作出现了异常,那么文件指向为空,就不需要关闭操作
                     {
                            try
                            {
                                   File_w.close();
                            }
                            catch (IOException e)
                            {
                                   System.out.println(e.toString());
                            }
                     }
              }
       }
}


        如果文件成功创建或者调用,那么结尾处,不论其操作过程中是否出现异常,我们都得关闭对应的流,以保证资源的合理使用。所以,我们将对应的close放入finally的程序块中,并加上非空判断条件,来保证流被关闭。注意上例中的模块外声明。

 

        上面的文件调用方法,我们采用的是新建文件。如果想要对已经有了的文件进行续写。我们需要用下面这种格式:

        FileWriter File_w =new FileWriter("test.txt",true); //实现文件续写

        由于涉及文件的修改与录入,这里就有个细节需要注意。那就是,Windows无法识别转义字符"\n",如果想要换行,需使用"\r"。

 

 

字符流Reader

        读操作也是流操作,那么,就必然有关闭流的close函数。但是读操作的关闭流close,它与写操作不同的一点是,它不需要在关闭时调用flush刷新。只需要直接关闭流即可。这也说明了,读操作,不需要刷新。由此,我们可以看出,对应的读取流的整个操作过程。

        首先,读取流,想要读取数据,我们必须得有数据。那么,第一步就是将对应的文件,与读取流相关联。这一步,就是读取流的创建及初始化。然后,我们需要根据需求调用对应的流对象的read方法,来读取数据。具体的步骤,我们有下面这张图(创建省略):


图3.Reader字符流读操作顺序图

 

        读取数据用的是read系的函数,该函数系最大的特点就是以从前往后的顺序,来读取数据。注意,只有读取流才拥有mark、markSupport、ready、reset、skip方法。

        先来看read()方法。read( )方法读取的数据是挨个读取的,这也就是说,如果我们想要读取一个文件中的所有数据,我们必须得多次循环才行。而且,read()方法返回值类型为int型,这就是说,如果我们想要使他有意义的显示,则需要根据具体的需求,将其强制转换为我们需要的数据类型才可以。接下来,我们就他的使用举例说明:

import java.io.*;
class  MyReaderTest
{
       public static void main(String[] args)
       {
              FileReader File_r = newFileReader("test.txt");
             
              //第一种读取文件中所有数据的方式:
              while (ture)
              {
                     int buff = File_r.read();//read方法,无数据时会返回-1
                     if (buff==-1)
                     {
                            break;
                     }
                     sop<char>(buff);
              }    
             
              /*当然,上面哪种方式,再循环判断条件上使用了死循环条件,
               这样是不太科学的。编程中,我们不建议使用这种条件,哪怕
               不会出现死循环。于是,我们有下面这种写法:
              */
 
              //第二种方式:
              int n = 0;
              while ((n = File_r.read())!=-1)
              {
                     sop<char>(n);
              }
              //这样的写法,既简便,又安全
       }
       public static <T> void sop(T obj)
       {
              System.out.print(obj);  //这里不使用println换行,是为了保证数据的视觉连续
       }
}

        当然,我们也可以将文件中的数据一次性,或者分段,直接传入到一个数组中。对应的使用的是read( char[] f)和read(char[] f, int b, int len ),后者是将数据读入到数组对应的字段中。具体事例:

import java.io.*;
class MyReaderTest_2
{
       public static void main(String[] args)
       {
              FileReader File_r =newFileReader("test.txt");
             
              //读取数据入数组,这种read方法会返回读取的有效数据个数
              char[] c = new char[4];  //一般缓冲区,在使用时会定义为1024的整数倍
              int num = 0;
              while ((num = File.read(c))!=-1)
              {
                     sop(new String(c,0,num));
              }
       }
       public static <T> void sop(T obj)
       {
              System.out.print(obj);
       }
}

        上面的两个有关read的例子,都没有对读取流的异常进行处理,这是为了简便。正常写程序的时候,应该加上异常处理的语句。和写入流一样,最好不要抛出,而是内部处理。写处理语句的原则,和Writer一致。在读取时,没有读到文件抛出的异常是FileNoFoundException的IOExecption子类异常。

        现在,我们掌握了字符流的基本操作,那么我们就可以结合两者,来看一下他们在程序中的读写文字操作。这样的典型,就是复制操作。如下: 

/**@author:LZ
*/
/*目标:将本Java程序在D盘做个备份(即,copy操作)
  思路:1 创建读取流,读取源文件
              2 创建写入流,将读取入缓冲的数据写入
              3 关闭读写流
*/
import java.io.*;
class CopyTest
{
       public static void main(String[] args)
       {
              FileWriter w = null;
              FileReader r = null;
              char[] buffer = new char[1024]; //创建缓冲
              int num = 0;
              try
              {
                     r = newFileReader("CopyTest.java");
                     w = newFileWriter("D:\\CopyTest_backup.java");
                    
                     while ((num =r.read(buffer))!=-1)  //copy操作
                     {
                            w.write(buffer,0,num);
                     }
                     sop("copysuccess!");
              }
              catch (IOException e)  //源文件读取及新文件创建异常处理
              {
                     throws newRuntimeException("process:copy_error ")
                     sop(e.toString());
              }
              finally
              {
                     //关闭读写流
                     myClose(r);
                     myClose(w);
              }
       }
 
       private static <T extends java.io>void myClose(T t)
       {
              if (t!=null)
              {
                     try
                     {
                            t.close();
                     }
                     catch (IOException e)
                     {
                            sop(e.toString());
                     }
              }
       }
 
       public static <T> void sop(T t)
       {
              System.out.println(t);
       }
}

        上面就是字符流的基本操作了。为了提高对数据的读写效率,我们设立了缓冲区,因此,我们有对应的类BufferedWriter和BufferedReader。缓冲区主要实现了对数据的暂存,同时在需要的时候,能够将对应的数据一次性写入到对应文档中。这种性质,实际上是通过在缓冲区类中,封装数组来实现的。因为缓冲区都是依托于流而存在的,所以,具体的缓冲区对象,在初始化的时候,必须为其指定对应的可操作流对象。

        但是需要在意的是,关闭缓冲区的close方法。该方法本质上其实是关闭该缓冲区所对应的流对象。所以,在调用了缓冲区close方法后,不必在用对应流调用流的close方法了。

 

写缓冲区 BufferedWriter

        来看写缓冲BufferedWriter。对于写缓冲区来说,每次通过缓冲区进行的write写操作,都应该让写缓冲刷新一次。这个flush方法,同样是对相应流的操作,只不过信息量叫直接的大。写缓冲类中新增加了插入分隔符的newLine功能方法。newLine能够在调用缓冲中写入一个分隔符,来存到对应文件中。新定义这个方法,主要是为了避免不同系统下的换行转义字符冲突,来提高兼容性。比如Linux和Windows的换行就分别是"\n"和"\r"。这个方法是缓冲区独有的。

import java.io.*;
class  BufferedWriterTest
{
       public static void main(String[] args)
       {
              FileWriter w = newFileWriter("bftest.txt"); //创建测试文件
              BufferedWriter bw = newBufferedWriter(w); //将缓冲与流建立关系
              bf.write("1234adf");
              bf.flush();
              bf.writer("qe");
              bf.close();  //关闭对应流,在此之前刷新
       }
}

 

读缓冲区 BufferedReader

        读缓存BufferedReader中主要新提供了一个更加有效的方法readLine( )。这个方法能够从对应的读取流中获得相应文件的一整行(按照"\n"、"\r"、"\r\n"来区分行)数据的内容,返回值类型为String。它的使用方式如下:

import java.io.*;
class  BufferedReaderTest
{
       public static void main(String[] args)
       {
              //创建读取流,并关联相关文件
              FileReader r = newFileReader("bftest.txt");
              //创建缓冲区,并关联对应读取流,以提高读取效率
              BufferedReader br = newBufferedReader(r); //关联读缓冲到对应流
             
              String line = null;
              while ((line=br.readLine())!=null)//读取一行,如读到末尾则返回null
              {
                     sop(line);  //输出该行
              }
       }
 
       public static <T> void sop(T t)
       {
              System.out.println(t);
       }
}

        readLine方法,主要是为了提高文件获取速率,并简化读取过程。注意其返回值和read方法的不同,末尾时返回null,返回值不包含换行转义字符。

        既然学了缓冲区,那么我们可以通过缓冲区来实现上文中的复制操作: 

/**@author:LZ
*/
/*目标:将本Java程序在D盘做个备份(即,copy操作)
  思路:1 创建读取流,关联读缓冲区,读取源文件
              2 创建写入流,关联写缓冲区,将读取入缓冲的数据写入
              3 关闭读写流
*/
 
import java.io.*;
class  BufferCopyTest
{
       public static void main(String[] args)
       {
              //变量外部声明
              FileWriter w = null;
              FileReader r = null;
 
              BufferedReader br = null;
              BufferedWriter bw = null;
              try
              {
                     //建立读写流,并关联相应的缓冲
                     r = newFileReader("BufferCopyTest.java");
                     w = newFileWriter("D:\\TestCopy_backup.java");
                     BufferedReader br = newBufferedReader(r);
                     BufferedWriter bw = newBufferedWriter(w);
 
                     String s = null;
                     while ((s =br.readLine())!=null)
                     {
                            bw.write(s);
                            bw.newLine();
                     }
              }
              catch (IOException e)
              {
                     sop("reader &write_error");
              }
              finally
              {
                     MyClose(bw);
                     MyClose(br);
              }
       }
 
       //缓冲区关闭方法close及异常处理的封装函数
       public static <C extends java.io>void MyClose(C c) throws RuntimeException
       {
              if(c!=null)
              {
                     try
                     {
                            c.close();
                     }
                     catch (IOException e)
                     {
                            throw newRuntimeException("cant get file");
                     }
              }
 
       }
 
       public static <T> void sop(T t)
       {
              System.out.println(t);
       }
}


装饰设计模式

        装饰类是对已有的类的装饰,即功能上的增强。基于被装饰类的功能,而提供在此基础上更强的功能。由此,我们可以看出,对应的BufferedReader和FileReader的关系就似如此。常见的定义方式,常常把被装饰的类的对象封装到对应的装饰类中。然后,通过装饰类的构造函数,来完成对象传递。随后再根据对象的功能,来改进提供更强的功能。

class A extends Object//原类
{
       public void properties() //原有方法
       {
              System.out.println("desk");
       }
}
class B  //修饰类
{
       private A r;
       B(A r )
       {
              this.r = new A(r);
       }
 
       public void moreProperties()  //修饰后的新方法
       {
              r. properties ();
              System.out.println("adv_p_computer");
       }
}
classTestForDecoration
{
       public static void main(String[] args)
       {
              //修饰前后比较:
              A a = new A();
              a.properties();
              B b = new B(a);
              b.moreProperties();
       }
}

        我们一般将修饰类与被修饰的单个或多个类归属到同一个父类之下。通常而言为了提高对应建立的修饰类的扩展性,我们一般通过多态的向上转型的方式,来实现对被修饰类的调用。这样做是为了避免每次需要修饰时,都需要新建立对应的用于修饰的子类。即,摆脱继承结构,使用组合结构。避免了继承结构体系的臃肿,更具有灵活的特性,同时,降低了类与类之间的相互联系。利于模块化程序设计思想。组合结构,就是内部包含对应被调用类的形式的一种类结构。

        在继承父类抽象方法的情况,如果是创建抽象类,那么我们可以通过其内部的父类多态,来调用对应被修饰类的已经覆写过的原抽象方法,来避免重写代码造成的代码冗余。

        上文中的两种Buffer缓冲区类型类就是修饰类的典型。

 

LineNumberReader类

        这是所谓的lineNumber行号类,该类继承自IO修饰类BufferedReader,并在原有的基础上,新增加了设置行号setLineNumber( int lineNumber)方法,和获取行号getLineNumber()。这两个方法主要就是设置起始行号,和获取每一行是第几行。简单的来说,就是加了个计数器而已。

 

 


------- android培训java培训、期待与您交流! ----------


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值