跟着老毕学Java之IO字符流

 

IO

Java中的IO操作API封装在java.io包中,用来处理设备间的数据传输。按流向可分为输入流和输入流,按操作的数据可分为字节流和字符流。字符流操作的是字符,字节流操作的是一个个的字节。最早先有有字节流,后来为了方便人们操作,将字节编码制成了编码表,由此在字节流的基础上利用编码表产生了字符流。

JavaIO流中主要由四个基类:

WriterReader,它俩是所有字符流的基类;

InputStreamOutputStream,它俩是所有字节流的基类。

字符流

Writer

体系结构:

|-----------Writer

         |----OutputStreamWriter:字符流通向字节流的桥梁,可以指定编码。

           |--FileWriter:文件写入流,会创建一个文件,通过构造函数定义是续写还是覆盖。

         |----BufferedWriter:字符写入流,定义了缓冲区,可以提高效率;缓冲区为字符数组,可定义缓冲区大小,默认缓冲区大小为8k

代码示例

 

package bxd.day18;

/*

演示对已有文件写入的基本操作、异常处理机续写方式。

*/

import java.io.*;

class FileWriterDemo3

{

    publicstaticvoid main(String[] args)

    {

        FileWriterfw = null;//这句要定义在异常处理外部,提升其作用域,否则在finally中读不到fw。

        try

        {

            //文件续写:传递一个true参数,代表不覆盖一个已有的文件,并在已有文件的末尾处进行数据的续写。

            fw= newFileWriter("demo.txt",true);

            /*

            fw= new FileWriter("demo.txt");

            这句和注释外那句的区别:

            创建一个FileWriter对象,该对象已被初始化就必须要明确要被操作的文件。

            而且该文件会被创建到指定目录下。如果该目录下已有同名文件,则同名文件将被覆盖,。

            其实该步就是在明确数据要存放的目的地。

            */         

            

            /*

             write可以写入字符、字符数组(或字符数组的一部分)、字符串(或字符串的一部分)。

             注意:windows中,换行用"\r\n"两个字符表示,而Lunix中用"\n"一个字符表示。

             所以,若只用“\n”,使用windows自带的记事本不能看到换行效果。

             */        

            fw.write("nihao\r\nxiexie");           

            //刷新流对象中的缓冲中的数据。将数据从内存中刷到目的地中。

            fw.flush();         

        }

        catch (IOException e)

        {

            System.out.println(e.toString());

        }

        finally

        {

            try

            {

                //这里必须判断一下,因为如果fw的初始化没有成功,会抛出空指针异常。这里的fw操作是针对fw.write()方法抛出异常后进行的处理。

                if(fw!=null)

                    //IO底层调用了系统的资源,IO流建立成功使用结束时,一定要释放资源。fw.close()之前,会先调用flush方法,

                    //这两个方法的区别是::flush刷新后,流可以继续使用;close刷新后,会将流关闭,若再次使用流会抛出异常。

                    //注意:如果没有调用flush也没用调用close方法,那么磁盘文件中看不到写入效果。                

                    fw.close();

            }

            catch (IOException e)

            {

                System.out.println(e.toString());

            }

        }       

        

    }

}


Reader

|-----------Reader

         |----InputStreamReader:字节流通向字符流的桥梁,可以指定编码。

           |--FileReader:文件读取流,文件需存在,否则抛出异常。      

         |----BufferedReader:字符读取流,定义了缓冲区,可以提高效率;缓冲区为字符数组,可定义缓冲区大小,默认缓冲区大小为8k

Reader

文件读取的第一种方式read()方法,一个字符一个字符的读取,返回值是字符的编码值,如果到流结束处,则返回-1,它能读取到换行符。

代码2

 

/*

第一种读取方式:一个字符一个字符的读取

*/

import java.io.*;

importstaticjava.lang.System.*;

class FileReaderDemo

{

   publicstaticvoid main(String[] args)throws IOException

   {

      

      //创建一个文件读取流对象,和指定名称的文件相关联。

      //要保证该文件是已经存在的,如果不存在,会发生异常:FileNotFoundException。

      FileReaderfr = newFileReader("demo.txt");

      

 

      int ch =0;

      while((ch=fr.read())!=-1)

      {

         out.println((char)ch);

      }

 

      /*

      while(true)

      {

         intch = fr.read();

         if(ch== -1)

            break;

         out.println((char)ch);

      }

      */

 

      /*

      intch =fr.read();

      out.println("ch="+(char)ch);

      intch1 =fr.read();

      out.println("ch1="+(char)ch1);

      intch2 =fr.read();

      out.println("ch2="+(char)ch2);

      intch3 =fr.read();

      out.println("ch3="+ch3);

      */

 

      fr.close();

   }

}


文件读取的第二种方式read(char[]ch),把读取的字符放入字符数组中,返回的读取的字符个数,如果到流结束处,则返回-1;它能读取到换行符。

代码3

 

/*

第二种读取方式:通过字符数组进行读取。

*/

import java.io.*;

importstaticjava.lang.System.*;

 

class FileReaderDemo2

{

   publicstaticvoid main(String[] args)throws IOException

   {

      FileReaderfr = newFileReader("demo.txt");

 

      //定义一个字符数组(也可称为缓冲区),用于存储读到的字符。

      //该reader(char[])返回的值是读到的字符个数。

      char [] buf =newchar[1024];//通常情况下数组的长度会定义为1024的整数倍

 

      int num =0;

      while((num=fr.read(buf))!=-1)

      {

         out.println("num="+num+"...."+new String(buf,0,num));

         //为什么不用new String(num),因为如果最后一次读取的个数如果不满足1024个,那么会把上次取出而本次没有覆盖掉的字符也打印出来。

      }

      /*

      intnum =fr.read(buf);

      out.println("num="+num+"...."+newString(buf));

      intnum1 =fr.read(buf);

      out.println("num1="+num1+"...."+newString(buf));

      intnum2 =fr.read(buf);

      out.println("num2="+num2+"...."+newString(buf,0,num2));

      intnum3 =fr.read(buf);

      out.println("num2="+num3+"...."+newString(buf));

      */

 

      fr.close();

      

   }

}


综合练习:文件复制

/*

将c盘的一个文本文件复制到D盘。

 

复制原理:

其实就是C盘下的文件数据存储到D盘的一个文件中。

 

步骤:

1.在D盘创建衣蛾文件,用于存储C盘文件中的数据。

2.定义读取流和C盘文件关联。

3.通过不断的读写完成数据存储。

4.关闭资源。

*/

import java.io.*;

class CopyTest

{

   publicstaticvoid main(String[] args)throws IOException

   {

      //myCopy("B:\\无限小说网_55x.cn_遮天.txt","D:\\无限小说网_55x.cn_遮天.txt");

      //copy_1();

      copy_2();

      

   }

   public staticvoidmyCopy(String sour,String dest)throws IOException

   {

      FileReaderfr = newFileReader(sour);

      FileWriterfw = newFileWriter(dest);

 

      char[] buf =newchar[1024];

      int num =0;

      while((num =fr.read(buf))!=-1)

      {

         fw.write(buf,0,num);       

      }

      fr.close();

      fw.close();

   }

   publicstaticvoid copy_2()

   {

      FileWriterfw = null;

      FileReaderfr = null;

      try

      {

         fw= newFileWriter("d:\\无限小说网_55x.cn_遮天.txt");

      

         fr= newFileReader("B:\\无限小说网_55x.cn_遮天.txt");

         char [] buf =newchar[1024];

         int len =0;

         while((len=fr.read(buf))!=-1)

         {

            fw.write(buf,0,len);

            //其实fw里也有一个缓冲区,用于存放要写的内容,当缓冲区满了,自动调用底层方法,写入文件中。

         }

      }

      catch (IOException e )

      {

         thrownew RuntimeException("读写失败");

      }

      finally

      {

         if(fr!=null)

            try

            {

                fr.close();

            }

            catch (IOException e )

            {

            }

         if(fw!=null)

            try

            {

                fw.close();             

            }

            catch (IOException e )

            {

            }

         

      }

/*

      finally

      {        

         try

         {

            if(fr!=null)

            fr.close();

         }

         catch(IOException e )

         {

         }

         finally

         {

            

            try

            {

                if(fw!=null)

                fw.close();             

            }

            catch(IOException e )

            {

            }

         }

         

      }

 

      

*/   

   }

 

   publicstaticvoid copy_1()throws IOException

   {

      //创建目的地

      FileWriterfw = newFileWriter("c:\\无限小说网_55x.cn_遮天.txt");

 

      //与已有的文件关联

      FileReaderfr = newFileReader("b:\\无限小说网_55x.cn_遮天.txt");

 

      int ch =0;

      while((ch = fr.read())!=-1)

      {

         fw.write(ch);

      }

      fw.close();

      fr.close();

   }

}


字符流缓冲区

缓冲区的出现提高了对数据的读写效率,它要结合流才可以使用,它在流的基础上对流的功能进行了加强。对应的类:BufferedWriterBufferedReader

BufferedWriter

 

/*

缓冲区的出现是为了提高流的操作效率而出现的。

所以在创建缓冲区之前,必须要先有流对象。

该缓冲区中提供了一个跨平台的换行符。

newLine();

 

*/

import java.io.*;

class BufferedWriterDemo

{

   publicstaticvoid main(String[] args)throws IOException

   {

      //创建一个字符续写入流对象。

      FileWriterfw = newFileWriter("buf.txt",true);

 

      //为了提高字符写入效率,加入了缓冲技术。

      //只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。

      BufferedWriterbufw = newBufferedWriter(fw);//原理是这个对象里边封装了数组。

      

      for(int x = 1 ;x < 5; x++)

      {

         bufw.write("abcde"+x);

         bufw.newLine();

         bufw.flush();

 

      }

 

      //记住,只要用到缓冲器,就要记得 刷新。

      //bufw.flush();

      //其实关闭缓冲区,就是在关闭缓冲区中的流对象。

      bufw.close();

 

      

 

   }

}


BufferedReader

 

import java.io.*;

class BufferedReaderDemo

{

   publicstaticvoid main(String[] args)throws IOException

   {

      //创建一个读取流对象和文件想关联。

      FileReaderfr = newFileReader("buf.txt");

 

      //为了提高效率,加入缓冲技术,将字符读取流对象作为参数传递给缓冲对象的构造函数。

      //默认的缓冲区char[]数组大小为8k。

      BufferedReaderbufr = newBufferedReader(fr);

 

      Stringline = null;

      while((line=bufr.readLine())!=null)

      {

         System.out.println(line);

      }

 

      bufr.close();

 

   }

}


代码4:利用缓冲区拷贝文件

 

/*

通过缓冲区复制一个.java文件。

*/

 

import java.io.*;

class CopyTextByBuf

{

   publicstaticvoid main(String[] args)//throws IOException

   {

      BufferedReaderbufr = null;//如果不先定义为空,finally中就无法访问读写缓冲区的引用

      BufferedWriterbufw = null;

      try

      {

         bufr= newBufferedReader(newFileReader("BufferedReaderDemo.java"));

         bufw= newBufferedWriter(newFileWriter("B:\\BufferedReaderDemo.java"));

 

         Stringline = null;//line此时即是中转站       

         

         while((line =bufr.readLine())!=null)

         {//readLine只返回该行的有效内容,并不返回换行符,所以写入的试试,要加换行符或调用newLine方法。

            bufw.write(line);

            bufw.newLine();

            bufw.flush();

         }        

      }

      catch (IOException e)

      {

         thrownew RuntimeException("读写失败");

      }

      finally

      {        

         try

         {

            if(bufr!=null)

                bufr.close();

         }

         catch (IOException e)

         {

            thrownew RuntimeException("读取关闭失败");

         }     

         try

         {

            if(bufw!=null)

                bufw.close();

         }

         catch (IOException e )

         {

            thrownew RuntimeException("写入关闭失败");

         }

      }

   }

 

}

readLine方法原理:

读一行,获取读取的多个字符,最终都是在硬盘上一个一个读取,所以最终使用的还是read()方法一次读取一个的方法(因为只有一个一个读取,它才能筛选判断出换行符,判断一行是否到结尾)。只不过它不是读一个就写一个,而是读一行就放入缓冲区,读完一行再,再刷新缓冲区,把缓冲区的数据写入硬盘文件中。

readLine返回的是字符串,如果读到末尾,返回值为null;这是与read不同的地方。

自定义读取流缓冲区与装饰设计模式

自定义缓冲区,模拟BufferedReader,而BufferedReader用到了专属设计模式,所以要先明白装饰设计模式。

装饰设计模式

装饰设计模式:当想要对已有的对象进行功能增强时,可以定义类,将已有的对象传入,基于已有的功能,并提供加强功能。那么自定义的该类称为装饰类。

装饰类的特点:装饰类通常会通过构造方法接受被装饰的对象。并基于被装饰的对象的功能,提供更强的功能。

装饰和继承的区别

一开始我们定义一个读取数据的类MyReader,其基本体系如下:

 

MyReader

         |---MyTextReader

         |---MyMediaReader

         |---MyDataReader


        

过一段时间,出现了新的技术(缓冲区),如果是使用继承,那么其他体系结构如下:

MyReader

         |---MyTextReader

                   |---MyBufferedTextReader

         |---MyMediaReader

                   |---MyBufferedMediaReader

         |---MyDataReader

                   |---MyBufferedDataReade


发现其体系臃肿,而且每定义一个新的子类都要再定义一个新的基于它的MyBuffered子类。那么可以考虑在设计体系时,将实现新技术的类与之前类体系的关系由继承关系转为组合,使用装饰模式。

 

classMyBufferedReader

{

         MyBufferedReader(MyBufferedTextReadertext)

         {}

         MyBufferedReader(MyBufferedMediaReadermedia)

         {}

}


上边这个类扩展性很差,因为每增加一个子类,都要修改构造函数,那么找到其参数的共同类型,通过多态的形式,提高扩展性。这个类设计如下:

 

classMyBufferedReader extends MyReader

{

         MyBufferedReader(MyReader r )

         {

         }

}


最终体系如下:

 

MyReader

         |---MyTextReader            

         |---MyMediaReader        

         |---MyDataReader

         |---MyBufferedReader


装饰模式比继承要灵活,避免了继承体系的臃肿,而且降低了类与类之间的关系。

装饰类因为是增强已有对象,具备的功能和已有对象的功能是相同的,只不过是提供了更强的功能所以装饰类和被装饰类通常是都属于一个体系中。

设计时,可以写继承,但如果过于臃肿,可以考虑采用装饰设计模式。

代码示例:

 

/*

明白了BufferedReader类中特有方法readLine的原理后就可以自定义一个类中包含一个功能和readLine一致的方法,来模拟下BufferedReader

*/

import java.io.*;

import java.util.*;

class MyBufferedReader

{

    private FileReaderfr = null;

    MyBufferedReader(FileReader fr)

    {

       this.fr =fr;

    }

    //这里方法处理异常时为什么是抛而不是try?因为这个问题,你没法解决,是产生在调用你功能的代码中,所以要抛而不能try。

    publicStringmyReadLine() throws IOException

    {

       //定义一个临时容器,原BufferedReader封装的是字符数组。

       //为了演示方便,定义一个StringBuilder容器,因为最终还是要将数组变成字符串。

       StringBuilder sb = new StringBuilder();

       int ch =0;

       while((ch =fr.read())!=-1)

       {

          if(ch=='\r')

             continue ;

          /*

          问题,这里为什么要把'\r'也判断一次?直接判断是不是‘\n’不可以吗?

          这里如果只判断'\r'或'\n',那么把'\r'存入sb中,要么在下次读取时把'\n'存入sb中,而我们需要的是'\r'和'\n'都不存入sb中。

          */

 

          if(ch=='\n')

             return sb.toString();

          else

             sb.append((char)ch);

       }

       //这里为什么要加一个判断?因为如果文件内容结尾处没有回车换行,没有这句判断,会丢失最后一行。

       if(sb.length()!=0)

          return sb.toString();

       

       returnnull;

       //return(sb.length!=0)?sb.toString():null;上边两句可以这样简写。

 

    }

    publicvoid myClose()throws IOException

    {

       fr.close();

    }

}

/*-----------------------------华丽的分割线----------------------------------*/

class MyBufferedReaderDemo

{

    publicstaticvoid main(String[] args)throws IOException

    {

       FileReader fr = new FileReader("CopyTextByBuf.java");

       MyBufferedReader myBuf = new MyBufferedReader(fr);

       String line = null;

       while((line = myBuf.myReadLine())!=null)

       {

          System.out.println(line);

       }

 

    }

}


LineNumberReader

LineNumberReader也是一个包装类,它在BufferedReader的基础上增加了一个记录行号的功能,而记录行号是在readLine方法中操作的,所以它继承了BufferedReader并复写了readLine方法,同时增加了getLineNumbersetLineNumber方法。

代码示例:

import java.io.*;

class LineNumberReaderDemo

{

    publicstaticvoid main(String[] args)throws IOException

    {

       FileReader fr = new FileReader("PersonDemo.java");

       LineNumberReader lnr = new LineNumberReader(fr);

 

       String line = null;

       lnr.setLineNumber(100);

       while((line=lnr.readLine())!=null)

       {

          System.out.println(lnr.getLineNumber());

       }      

       lnr.close();

    }

}

 

自定义带行号的缓冲区对象

因为之前自定义过缓冲区对象,可以通过继承这个缓冲区对象来实现。

代码示例

import java.io.*;

class MyLineNumberReaderextends MyBufferReader

{

    Reader r = null;

    intlineNumber = 0;

    MyLineNumberReader(Reader r)

    {

       super(r);

    }

 

    public String myReadLine()throws IOException

    {      

       lineNumber++;     

       returnsuper.myReadLine();

    }   

 

    publicvoid setLineNumber(int lineNumber)

    {

       this.lineNumber = lineNumber;

    }

    publicint getLineNumber()

    {

       returnlineNumber;

    }

}

 

————————————————————————————————————————————

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

你可能感兴趣的:(跟着老毕学Java之IO字符流)