java IO流 [缓冲技术] [装饰设计模式]

来源:互联网 发布:mac远程连接云服务器 编辑:程序博客网 时间:2024/06/05 19:11

 

 

  1. IO流
  2. 流的分类
  3. 字符流的由来
  4. 乱码的出现
  5. 读写流
  6. 流的读写操作
  7. Writer
  8. Reader
  9. 缓冲技术
  10. IO异常
  11. 装饰设计模式
  12. InputStreamReader

  • IO流

           IO流---input 和 output 的简写,用来处理设备之间的数据传输 如内存 硬盘 网络等之间的数据传输
           IO包---用于操作流的对象封装在IO包中

 

  • 流的分类



            流按操作数据分 : 输入流 输出流

            流按流向分 : 字节流 字符流


 早期IO包出现的都是基于字节流,因为所有数据都是字节数据
 而为了方便操作字符,出现了字符流

  • 字符流的由来


ASCII 码表 美国信息交换标准码(American Standard Code for Information Interchange)

GBK 全名为汉字内码扩展规范,英文名Chinese Internal Code Specification亦采用双字节表示,总体编码
        范围为8140-FEFE,首字节在81-FE 之间,尾字节在40-FE 之间,剔除 xx7F一条线。
         总计23940 个码位,共收入21886个汉字和图形符号,其中汉字(包括部首和构件)21003 个,图形符号883 个。

Unicode码表:国际通用的编码表都用两个字节表示 Java的内核和class文件是基于unicode的,这使Java程序具有良好的跨平台性
        对可以用ASCII表示的字符使用UNICODE并不高效,因为UNICODE比ASCII占用大一倍的空间,而对ASCII来说高字节的0对他毫无用处。

UTF-8编码是一种兼容所有语言的编码方式,是UNICODE的一种变长字符编码又称万国码
        UTF-8用1到6个字节编码UNICODE字符。用在网页上可以同一页面显示中文简体繁体及其它语言(如日文,韩文)

  • 乱码的出现


开发和编译代码时指定字符集与运行操作系统的默认编码表不一致
先Java(包括JSP)源文件中很可能包含有中文,而Java和JSP源文件的保存方式是基于字节流的,如果Java和JSP编译成class文件过程
中,使用的编码方式与源文件的编码不一致,就会出现乱码。

  • 读写流

Java读写文件最常用的类是
FileInputStream/FileOutputStream和FileReader/FileWriter。其中FileInputStream
和FileOutputStream是基于字节流的,常用于读写二进制文件。

读写字符文件建议使用基于字符的FileReader和FileWriter,省去了字节与字符之间的转换。
但这两个类的构造函数默认使用系统的编码方式,如果文件内容与系统编码方式不一
致,可能会出现乱码。

  • 流的读写操作


Reader 和 Writer 是字符流的两个基类 两个专门操作字符流的类  适用于处理字符数据

InputStream 和 OutputStream 是字节流的两个基类  是专门用来操作字节的 可以操作一切数据,通用性强

 

所有由以上四个类都是抽象类 , 由它们派生的子类名都是以基类为后缀,以功能名作为前缀

 

  • Writer


Writer 操作字符输入流的抽象类 , 子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。
 |        多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。 Writer  构造方法被protected 修饰表明只有子类可以访问
 |----BufferedWriter  将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。特有方法 newLine()
 |----CharArrayWriter
 |----FilterWriter
 |----PipedWriter
 |----PrintWriter  此类实现在 PrintStream 中的所有 print 方法 可以接收 字节流也可以接收字符流  可以设置自动刷新   
 |----StringWriter ---特有方法 StringBuffer getBuffer()    返回该字符串缓冲区本身,当前缓冲区值的 StringBuffer。       
 |----OutputStreamWriter 字符流通向字节流的桥梁 ,将要写入流中的字符编码成字节,字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。

        |                                每次调用 write() 方法都会导致在给定字符(或字符集)上调用编码转换器。为了获得最高效率,可考虑将 OutputStreamWriter

        |                                 包装到 BufferedWriter 中,以避免频繁调用转换器。例如: 

        |                                Writer out = new BufferedWriter(new OutputStreamWriter(System.out));                          
        |----FileWriter  写文件的便捷类 ,用于写入字符流。

 

他们都实现了 Appendable, Closeable, Flushable 接口

    • Writer 的基本方法


  Writer append(字符或字符序列) 将指定字符或字符序列添加到此流
  abstract  void close()   关闭此流,但要先刷新它。            ------抽象方法,子类需要实现
  abstract  void flush()   刷新该流的缓冲。               ------抽象方法,子类需要实现
  abstract  void write(char[] cbuf, int off, int len)   写入字符数组的某一部分。------抽象方法,子类需要实现
  void write(int c)  写入单个字符。                    
  void write(String str)   写入字符串。             
  void write(char[] cbuf)  写入字符数组。 
  void write(String str, int off, int len)   写入字符串的某一部分。     
    

    • FileWriter

      • FileWriter的构造方法

public FileWriter(String fileName) throws IOException
    ┗----- 根据给定的文件名构造一个 FileWriter 对象时将在指定的目录下创建指定文件,若该目录已有同名文件,则将其覆盖。
    fileName - 一个字符串,表示与系统有关的文件名。
    必须抛出:
    IOException - 如果指定文件存在,但它是一个目录,而不是一个常规文件;或者该文件不存在,但无法创建它;
                           或因为其他某些原因而无法打开它 则抛出该异常
    
    
public FileWriter(String fileName, boolean append) throws IOException
     ┗-----根据给定的文件名以及指示若存在同名文件是否会覆盖 append为 true表示续写 

  注意:

    1)创建流对象要声明抛出 IOException 异常 或try{} catch{} 处理
    2)创建的 FileWriter 对象调用write方法时 是将数据写入流当中,而不是文件中
    3)调用flush方法将流中的缓冲数据刷入目的文件中,并且流中的数据还存在于流中,可以继续使用
    4)调用close方法也可以将流中的数据刷到目的地,但会关闭流,所以流中的缓冲数据将被被清除
    5)FileWriter 专门负责字符文件写入 该类没有无参构造函数说明对象一初始化就要指定写入文件

      • flush与close的区别:
         close --数据写入结束必须关闭此写入流,但要先刷新一次内部缓冲中的数据将数据刷到目的地中。
             在关闭该流之后,再调用 write() 或 flush() 将导致抛出 IOException。关闭以前关闭的流无效。
         flush -- 刷新后流对象还可以继续使用,流中的数据还存在

 

  • Reader

 

FileReader
 |  
 |----BufferedReader  从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。
 |                 |----LineNumberReader
 |----CharArrayReader
 |----FilterReader
 |                 |---PushbackReader
 |----StringReader
 |----PipedReader
 |----InputStreamReader 字节流通向字符流的桥梁 
                      |----FileReader  用来读取字符文件的便捷类 ,用于读取字符流
                  

    • FileReader 

    • FileReader 的构造方法


             FileReader(File file) throws FileNotFoundException
                 ┗在给定从中读取数据的 File 的情况下创建一个新 FileReader。
             FileReader(FileDescriptor fd) throws FileNotFoundException
                 ┗在给定从中读取数据的 FileDescriptor 的情况下创建一个新 FileReader。
             FileReader(String fileName) throws FileNotFoundException
                 ┗在给定从中读取数据的文件名的情况下创建一个新 FileReader。
           

        FileReader 的构造方法
        1)都是有参数构造函数 ,说明要将数据写入操作必须指定操作对象
        2)都要声明抛出 FileNotFoundException - 如果指定文件不存在,或者它是一个目录,而不是一个常规文件,抑或因为其他某些原因而无法打开进行读取。
        3)都假定默认字符编码和默认字节缓冲区大小都是适当的,若要自己指定 需要构造  InputStreamReader

      •  FileReader基本方法:


 abstract  void close()   关闭该流并释放与之关联的所有资源。 这是必须进行的操作  ----子类必须实现
                                     在关闭该流后,再调用 read()、 mark()、reset() 或 skip() 将抛出 IOException。关闭以前关闭的流无效。 
 abstract  int read(char[] cbuf, int off, int len)  将字符读入数组的某一部分。     ----子类必须实现

  void mark(int readAheadLimit)   标记流中的当前位置。
  void reset()  重置该流。
  long skip(long n)    跳过字符。
  boolean markSupported()  判断此流是否支持 mark() 操作。
  boolean ready()   判断是否准备读取此流。    

  int read()  每次读取一个字符,且下次自动读取下一个字符,当读取到文件末尾时返回 -1 (操作系统下文件结束标志)。
                 返回的是字符编码的整形表示 ,需要强转为字符
                 在字符可用、发生 I/O 错误或者已到达流的末尾前,此方法一直阻塞。
  int read(char[] cbuf)  将每次将指定长度(数组的长度)个字符读取存入数组中,且下次自动读取下一个指定长度的字符,
                  返回的是读取到的字符个数 ,若读取到文件末尾返回 -1
                  此数组称为写入缓冲区,
                  在某个输入可用、发生 I/O 错误或者已到达流的末尾前,此方法一直阻塞。

  int read(CharBuffer target)   试图将字符读入指定的字符缓冲区。


   
总结 :

Reader 的 read 方法是个阻塞式方法
copy 文件过程:
  1)创建目的地(目标路径及文件)
   通过构造函数创建 写入流对象 创建目的文件


  2)与已有文件关联
   通过构造函数创建 读取流对象 关联已有文件


  3)读取文件中数据
   为了提高读写效率,不要读一个写一个数据,而是先将读取到的数据存入缓冲区(数组)中,等缓冲区会数组满后再进行写入操作

   避免了每次调用read() 方法都会导致从底层输入流读取一个或多个字节 ,频繁启动转换器,效率低


  4)将读取到的数据写入目的文件
   将读取到并存储在缓冲区数组中的数据写入流中


  5)读写完毕  关闭两个流并释放与之关联的所有资源
  

 

  • 缓冲技术

   流 Stream --数据像流水一样从一处流向另一处 流表示是数据从源到目的端的流动


   输入操作时,字节流从输入设备流向内存;
   输出操作时,字节流从内存流向输出设备;
   流中的数据可以是字符 图片 音视频 等


   因为如果没有缓冲区,应用程序每次输入(读取)输出(写入)都要和设备进行通信,效率很低,

   因此缓冲区为了提高效率 ,数据读写时在内存中为每个流对象提供一个临时缓冲区,每次读取
   一个缓冲区大小的数据后再将这些数据一次性写入(输出)到目标设备

   字符流底层使用字节流的缓冲区,先将字节读取到缓冲区,然后经过查字符编码表 查到字符才进一步的对字符进行操作

 

   数据缓冲这个东西,就是因为数据被输入后在处理的时候需要一定的时间,为了输入接着输出,零时差,就需要缓冲了,
   先预读并处理一部分信息,然后开始输出,在输出的同时可以进行输入和处理,然后等缓冲的部分输出完后,
   另一部分的数据也处理完毕了,就可以接着输出了。

  根据处理速度的不同,需要的缓冲区大小也是不同的。
  倘若没有这个缓冲,那么就会很卡了,断断续续的数据流,因为处理不完。
  CPU 的处理速度 内存存取速度 硬盘(外存储设备)擦写速度 输入输出设备读取输出速度 差异很大

 因此 , 缓冲区起到缓解速度不匹配的问题
 如果从设备读取10M的文件,每次读取一个字节,完成操作将需要做10M次I/O操作,I/O操作又是一件相当耗时的事情,无疑在很大程度上降低了系统的性能。
提高读写效率,将要读写的数据存入指定大小缓冲区中,减少频繁的硬盘擦写操作

 

 

    • BufferedReader 

    •    ┗━从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。

                            Reader 所作的每个读取请求都会导致对底层字符或字节流进行相应的读取请求。因此,建议用 BufferedReader 包装所有其 read() 操作可能开销很高的

                             Reader(如 FileReader 和 InputStreamReader)。例如, BufferedReader in = new BufferedReader(new FileReader("foo.in"));
                             

                             如果没有缓冲,则每次调用 read() 或 readLine() 都会导致从文件中读取字节,并将其转换为字符后返回,而这是极其低效的。

   构造方法:
   BufferedReader(Reader in)
     ┗━  创建一个使用默认大小输入缓冲区的缓冲字符输入流。 所接收的参数为 Reader 子类字符写入流对象
   BufferedReader(Reader in, int sz)
      ┗━创建一个使用指定大小输入缓冲区的缓冲字符输入流。  所接收的参数为 Reader 子类字符写入流对象

 

 

   特有方法: readLine() 读取一个文本行。通过下列字符之一即可认为某行已终止:
                   换行 ('\n')、回车 ('\r') 或回车后直接跟着换行。 
                   返回:包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null ,所以在写入操作时需要另外添加换号符,,

    • BufferedWriter

        ┗━将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。

   构造函数:
   BufferedWriter(Writer out)
      ┗━创建一个使用默认大小输出缓冲区的缓冲字符输出流。 所接收的参数为 Writer 子类字符写入流对象
   BufferedWriter(Writer out, int sz)
      ┗━创建一个使用给定大小输出缓冲区的新缓冲字符输出流。 所接收的参数为 Writer 子类字符写入流对象

 

   特有方法:newLine() 写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义,

    利用缓冲技术拷贝文件 


   

 import java.io.*;    class CopyText    {     public static void  main(String[] args) throws IOException     {      //为了提高字符写入流效率 加入了缓冲流技术将需要被提高写入流效率的对象作为参数传入缓冲区的构造函数中      BufferedWriter bufw = null ;      BufferedReader bufr = null ;      try      {         bufw = new BufferedWriter(new FileWriter("tobuf.txt"));//字符写入流对象被封装进  写入缓冲区中            bufr= new BufferedReader(new FileReader("frombuf.txt"));//字符读取流对象被封装进  读取缓冲区中         String line = null ;       while ((line=bufr.readLine())!=null)       {        bufw.newLine();//newLine()方法是 BufferedReader 的特有方法, 该方法具有跨平台性的行分隔作用        bufw.flush();//使用 缓冲技术 写入字符到字符输出流中时 为了避免写入意外中断而导致数据丢失必须 刷新一次       }      }      catch (IOException e)      {      }      finally      {        try       { if(bufw!=null)        bufw.close(); //关闭缓冲区 就是关闭缓冲区中的流对象       }       catch (IOException e)       {       }               try       { if(bufr!=null)        bufr.close(); //关闭缓冲区 就是关闭缓冲区中的流对象       }       catch (IOException e)       {       }      }   }


 

 


         readLine() 的内部是通过read来实现的 , 通过一个一个字符的读取,并将字符存入内存中的字符数组中.直到读取到分隔符位置
                          时将读取并存在数组中的字符数组转换成字符串并返回,数组中的内容不包括换行符

 

   模拟 readLine() 方法

  class MyBufferedReader  {   private FileReader r;   MyBufferedReader(FileReader r)   {    this.r=r;   }   public String myReadLine() Throws IOException   {    //定义临时容器. 用 BufferedReader 封装的是字符数组    //定义一个 StringBuilder 容器 因为最终将数据转成字符串    StringBuilder sb = new StringBuilder();    int ch = 0 ;    while((ch=r.read()!=-1)    {     if(ch=='\r')      continue;     if(ch=='\n')      return sb.toString();     else      sb.append((char)ch);    }    if(sb.length()!=0)     return sb.toString();    return null;   }   public void myClose() throws IOException    {    r.close();   }  }


 

 

 

 

 

 

  • IO异常

所有涉及到IO数据的操作都要声明抛出异常 IOException 或者对其进行异常处理

  • IO异常的处理方式

  将所有操作流的语句放入try块中
   try
   {
    操作流的语句
   }
   catch (IOException e)
   {
    异常处理
    System.out.println(e.toString());//提示
   }
   finally
   {// 创建了多少个流对象就必须分别的关闭
    try//涉及流操作都有处理异常 这里关闭资源要单独try
    { if(流对象引用不为空)//只有创建了流对象才能调用资源关闭操作否则导致空指针异常
     关闭资源操作  //关闭资源是必须进行的操作
    }
    catch (IOException e)
    {
     异常处理
    System.out.println(e.toString());

                           }
                            }

 



   
             

  • 装饰设计模式


定义类在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。
将已有对象作为参数传入,基于已有功能并提供功能加强的设计模式

装饰类,通过构造函数将已有对象的传入并进行功能增强的类
 

BufferedReader 就是一种装饰类

可以将缓冲技术单独抽取进行封装。
谁要缓冲区就将谁和缓冲相关联即可。
这样的设计的相对继承体系会变的简单。

    • 为什么要用使用装饰类 与使用子类继承有什么不同?


使用继承也可以对父类中的功能进行增强只需要调用super就可以调用父类的方法,那使用装饰和继承有什么区别

已有对象被传入装饰类后 装饰类内持有传入对象的引用,从而可以直接调用传入对象的所有功能,因此在装饰类内部

可以对存入对象的功能进行直接调用并加以功能增强,在原来对象的功能基础上添加更强的功能,当创建装饰类对象

时必须先将已有对象传入,然后就可以调用装饰类中的增强功能了

 装饰是对对象动态添加新状态或行为方法,只需对某个或某些行为功能进行增强, 而子类继承父类是静态继承
 装饰模式提供了“即插即用”的方式,在运行期间决定何时增加何种功能。且功能不影响以前的功能 就增加功能来说,
 装饰模式比生成子类更加灵活。

 装饰类和被装饰类通常是都属于一个体系中的。被传入对象的类具备同一个父类或接口,装饰类利用多态,
 可以接收该体系下的所有基本装饰某些基本 功能的对象


对于同一个父类的多个子类,他们都具备继承自父类的功能,但也都有自己独特的功能,在后期想要对子类的某个基本功能进行增强时,
只需要创建装饰类,他可以接收具备该基本功能的所有子类对象,对该基本功能进行增强,当被装饰对象传入该装饰类之后,装饰类就具备
被装饰对象的所有功能,同时又增强某个或几个功能,而采用继承的办法进行功能增强,则定义的子类只能对指定类进行增强,不同的类想要
对同一个功能进行增强时都要定义自己的子类,因此会显得很臃肿,而且使用不灵活

简单说就是装饰可以对同一体系下(利用多态特性,父类或者借口引用指向子类对象)的不同子类 的同一个基本功能进行功能增强,比较灵活
利用继承进行功能增强需要分别为每个需要增强的类定义子类,显得臃肿

如 FileReader 体系下都用到缓冲技术,如果每个子类都分别定义增加缓冲功能的子类,那么可以分别实现缓冲技术
 FileReader
   |----CharArrayReader
   |                |---BufferedCharArrayReader
   |----FilterReader
   |               |---BufferedFilterReader
   |----StringReader
   |               |---BufferedStringReader
   |----PipedReader
   |               |---BufferedPipedReader
   |----InputStreamReader
                   |---BufferedInputStreamReader
 如果定义装饰类 将需要增强的功能进行抽取 则体系变得简约
 FileReader
    |----CharArrayReader
    |----FilterReader
    |----StringReader
    |----PipedReader
    |----InputStreamReader
    |---BufferedReader   ---装饰类, 可以接收以上几个类的对象

BufferedReader 构造函数接收的类型为 Reader 利用多态特性 实现对同一体系下的不同对象进行装饰

 

  • InputStreamReader


     InputStreamReader是字节流通向字符流的桥梁, 读取字节并将其解码为字符。 每次调用 InputStreamReader 中的一个 read() 方法都会导致从底层输入流读取一个或多个字节。

            为了达到最高效率,可要考虑在 BufferedReader 内包装 InputStreamReader。例如:
           BufferedReader in  = new BufferedReader(new InputStreamReader(System.in));


InputStreamReader 转换流通过构造函数接收 InputStream 引用的字节输入流对象 将字节流转换成使用默认或者指定字符集的字符流

OutputStreamWriter 转换流通过构造函数接收 OutputStream 引用的字节输出流对象 将字符流转换成字节流

 

System.in 和 System.out分别是标准输入输出流
可以通过 System.setIn(InputStream in)  和 System.setOut(PrintStream out) 来分别改变他们的输入输出流

对键盘录入的数据进行一定的操作后通过控制台输出

//从标准输入流中获取字节流并传入转换流通过指定字符集转成字符,然后用缓冲类包装装饰BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); //通过装饰类装饰字符流进行字符进行一定的操作,然后转换成字节流 并通过标准输出流写入控制台上BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));   String line = null ;   while ((line=bufr.readLine())!=null)   {    if("over".equals(line))     break;    bufw.write(line.toUpperCase());//对输入流的字符数据进行转换操作,并写到字符写入流中    bufw.newLine(); //给流添加行分隔符    bufw.flush(); //将一行字符刷入显示器   }   bufr.close();


 

涉及三个过程 输入 处理 输出
1)首先是从标准输入流(键盘)中获取字节流并传入转换流通过指定字符集转成字符,然后用缓冲类包装装饰进行缓冲作用,一次读取一行
  字符流底层使用字节流的缓冲区,先将字节读取到缓冲区,然后经过查字符编码表 查到字符才进一步的对字符进行操作
2)然后对一行的字符串进行一定的操作(这里是小写转大写操作)
3)最后对操作完的存放于内存中的字符串(字符流)再转成字节流通过标准输出流输出到(屏幕)上

在转换过程涉及到查编码表编码解码操作
在字符串的小写转大写的时候涉及到CPU的处理速度
在读和写的时候都涉及到缓冲过程 该过程就是起到了缓解CPU处理速度与 内存与输入输出设备之间的传输字节流或字符流的速度
不匹配问题
缓冲区的大小可以根据CPU处理速度和输入输出设备读写速度不同来定