当前位置: 首页 > Thinking in Java > 正文

第18章 – Java IO 系统 – InputStream,OutputStream,Reader,Writer 总结与实例

目 录
 [ 隐藏 ]

1. 概述

   Java 的IO系统大致可分为两种:

   (1) 一种是以 字节 为导向的,表现为各种流(stream),包括输入流InputStream和输入流OutputStream.

   (2) 一种是以 字符 为导向的,表现为各种Reader(输入)和各种Writer(输出)

   另外,还有两个有用的类File和RandomAccessFile类

   (1) File类

       File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹. 

       File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、

       获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、

       删除文件和目录等方法.  

   (2)  RandomAccessFile类

该对象适用于由大小已知的记录组成的文件,所以可以用seek()将记录从一处转移到另一处.

该对象并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),

通过内部的指针来操作字符数组中的数据. 

 

该对象特点:

该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象.

该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)

       注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;

       如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容. 

       可以用于多线程下载或多个线程同时写数据到文件.

2 以 字节 为导向的 输入输出流

   InputStream是抽象类,其抽象方法read()需要子类来实现,不同的子类实现的read()方法都是从当前

   输入流中读取一个字节,读到最后是返回-1,否则返回一个int(0-255).

   注意,有的子类该read()方法是阻塞的,有的则是不阻塞的.

2.1 字节 导向的输入类InputStream

    InputStream用来表示从不同数据源产生输入的类.InputStream是抽线类实际使用中,不能直接使用这个类.

    针对不同的数据源有相关的该InputStream的派生类来专门处理对应数据源的输入.

    常用的数据源包括:

    (1) 字节数组

    (2) String对象

    (3) 文件

    (4) 管道,其工作方式与实际的管道相似,从一端输入,从另一端输出

    (5) 一个有其他种类的流组成的序列,以便我们可以将它们收集起来合并到一个新流中.

    (6) 其他数据源,如Internet连接等.

    值得说明的是(5),这里使用了包装器模式,将一种流包装成其他种类的流.Java IO中大量使用了这种模式.

    这个可以参考各种InputStream的构造函数即可.

    针对上面的不同数据源,Java IO中提供了与之对应的各种InputStream子类.

    InputStream的最主要的方法: read()方法是抽象的,需要子类实现.

2.1.1 ByteArrayInputStream

 

      表示数据源为字节数组的输入流.

      其构造函数为

      ByteArrayInputStream(byte[] buf) 创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组.

      这里传入一个字节数组即可.比如调用String.getBtyes()可以作为参数传递给构造函数.

      此类的read()方法不会阻塞

 

      示例:将字符串Hello World 你好 世界的UTF-8编码 转换成二进制字符串,并打印出来,

      每个字节用空格分割,最后打印字符串的UTF-8编码所占的字节数.

package io;

			import java.io.ByteArrayInputStream;
			import java.io.IOException;
			import java.io.UnsupportedEncodingException;
			
			public class TestByteArrayInputStream
			{
			  public static void main(String[] args)
			  {
			    String str = "Hello World 你好 世界";
			    ByteArrayInputStream bs = null;
			    try
			    {
			      bs = new ByteArrayInputStream(str.getBytes("UTF-8"));
			      int temp = 0;
			      int count = 0;
			      while(temp!=-1)
			      {
			        temp = bs.read();//当读取到最后时,返回-1
			        if(temp != -1)
			        {
			          System.out.print(Integer.toBinaryString(temp) + " "); 
			        }
			        count ++;
			      }
			      System.out.println();
			      System.out.println("总字节数: " + count);
			      
			    }
			    catch (UnsupportedEncodingException e)
			    {
			      // 捕捉字符集编码转换异常
			    }
			    finally
			    {
			      if(null != bs)
			      {
			        try
			        {
			          bs.close();
			        }
			        catch (IOException e)
			        {
			          //捕捉关闭文件失败的IO异常
			        }
			      }
			    }
			  }
			}

 

2.1.2 StringBufferedInputStream

 

     该类已弃用,此类未能正确地将字符转换为字节.

     从 JDK 1.1 开始,从字符串创建流的首选方法是通过 StringReader 类进行创建. 

 

2.1.3 FileInputStream

 

      FileInputStream  把一个文件作为 InputStream 用于从文件中读取数据

      构造函数为:

      FileInputStream(File file) 通过打开一个到实际文件的连接来创建一个 FileInputStream,

                                 该文件通过文件系统中的 File 对象 file 指定. 

FileInputStream(FileDescriptor fdObj) 通过使用文件描述符 fdObj 创建一个 FileInputStream,

                                     该文件描述符表示到文件系统中某个实际文件的现有连接. 

FileInputStream(String name) 通过打开一个到实际文件的连接来创建一个 FileInputStream,

                            该文件通过文件系统中的路径名 name 指定.

 

read() 方法从此输入流中读取一个数据字节.如果没有输入可用,则此方法将阻塞.                              

 

2.1.4 PipedInputStream

 

      产生用于写入相关PipedOutput Stream的数据.

      用于两个线程之间的通信.

      public int read() throws IOException读取此管道输入流中的下一个数据字节.

      返回 0 到 255 范围内的 int 字节值.

      在输入数据可用、检测到流的末尾或者抛出异常前,此方法一直阻塞.

 

      该方法没有抛出InterruptedException,表明该方法阻塞后不可中断.

 

      管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节.

      通常,数据由某个线程从 PipedInputStream 对象读取,

      由其他线程将数据写入到相应的 PipedOutputStream.

 

      不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程.

 

      管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开.

 

      如果向连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏.

 

      使用

      void connect(PipedOutputStream src) 使此管道输入流连接到管道输出流 src

      这个方法 将输入和输出管道连接起来.

      也可以使用对应的输出管道来连接输入管道.

 

      示例程序: 使用输入输出管道,从一个线程中输入数据,从另一个线程中读取出来.

      两个线程同时读,或者同时写管道是 相互之间不能互斥,所以,读写一般只能使用单独的线程.

 

      该示例程序来自: http://xouou.iteye.com/blog/1333475

package io;

			import java.io.PipedInputStream;
			import java.io.PipedOutputStream;
			import java.util.concurrent.ExecutorService;
			import java.util.concurrent.Executors;
			
			/*
			 * 管道流: PipedInputStream void connect(PipedOutputStream src) 
			 * 使此管道输入流连接到管道输出流 src PipedOutputStream void
			 * connect(PipedInputStream snk) 在JDK我们看到PipedInputStream中有管道缓冲区,
			 * 用来接收数据 管道流内部在实现时还有大量的对同步数据的处理
			 * 管道输出流和管道输入流执行时不能互相阻塞,所以一般要开启独立线程分别执行 
			 * 顺便复习了多线程操作 [示例]:管道流
			 */
			
			public class TestPipedInputOutputDemo
			{
			  public static void main(String[] args) throws Exception
			  {
			    PipedInputStream pin = new PipedInputStream();
			    PipedOutputStream pout = new PipedOutputStream();
			    pin.connect(pout); // 输入流与输出流连接
			
			    ExecutorService exec = Executors.newCachedThreadPool();
			    exec.execute(new ReadThread(pin));
			    //exec.execute(new ReadThread(pin)); //这里尝试两个线程 读取管道,导致异常
			    
			    exec.execute(new WriteThread(pout));
			    //exec.execute(new WriteThread(pout)); //这里尝试两个线程写入管道,导致异常
			    
			    exec.shutdown();
			    
			  }
			
			  public static void sop(Object obj) // 打印
			  {
			    System.out.println(obj);
			  }
			}
			
			class ReadThread implements Runnable
			{
			  private PipedInputStream pin;
			
			  ReadThread(PipedInputStream pin) //  
			  {
			    this.pin = pin;
			  }
			
			  public void run() // 由于必须要覆盖run方法,所以这里不能抛,只能try
			  {
			    try
			    {
			      sop("R:读取前没有数据,阻塞中...等待数据传过来再输出到控制台...");
			      byte[] buf = new byte[1024];
			      int len = pin.read(buf); // read阻塞
			      sop("R:读取数据成功,阻塞解除...");
			
			      String s = new String(buf, 0, len);
			      sop(s); // 将读取的数据流用字符串以字符串打印出来
			      pin.close();
			    }
			    catch (Exception e)
			    {
			      throw new RuntimeException("R:管道读取流失败!");
			    }
			  }
			
			  public static void sop(Object obj) // 打印
			  {
			    System.out.println(obj);
			  }
			}
			
			class WriteThread implements Runnable
			{
			  private PipedOutputStream pout;
			
			  WriteThread(PipedOutputStream pout)
			  {
			    this.pout = pout;
			  }
			
			  public void run()
			  {
			    try
			    {
			      sop("W:开始将数据写入:但等个5秒让我们观察...");
			      Thread.sleep(5000); // 释放cpu执行权5秒
			      pout.write("W: writePiped 数据...".getBytes()); // 管道输出流
			      pout.close();
			    }
			    catch (Exception e)
			    {
			      throw new RuntimeException(e.getCause());
			    }
			  }
			
			  public static void sop(Object obj) // 打印
			  {
			    System.out.println(obj);
			  }
			}

 

2.1.5 SequenceInputStream

 

     将两个或多个InputStream对象转换成一个InputStream对象.

     SequenceInputStream 表示其他输入流的逻辑串联.

     它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,

     接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止.

 

     构造函数:

     SequenceInputStream(Enumeration<? extends InputStream> e) 

     通过记住参数来初始化新创建的 SequenceInputStream,

     该参数必须是生成运行时类型为 InputStream 对象的 Enumeration 型参数. 

     SequenceInputStream(InputStream s1, InputStream s2)  

     通过记住这两个参数来初始化新创建的 SequenceInputStream

     (将按顺序读取这两个参数,先读取 s1,然后读取 s2),

     以提供从此 SequenceInputStream 读取的字节.

 

2.1.6 FilterInputStream

 

     因为上面的所有InputStream提供方法就只有read()方法,显然这个不够用,也用的不方便.

     于是JDK中又提供了一些接口,比如DataInput,就有方法readByte(),readInt()等好用的方法.

     并且,还使用了装饰器模式,将一个普通的InputStream装饰成实现了DataInput接口的类,

     比如 DataInputStream:

 

     public class DataInputStream extends FilterInputStream implements DataInput

     有上面的类定义可知,FilterInputStream是就是装饰器的基类

 

     实现了各种好用接口的装饰器类(DataInputStream等)都是继承自FilterInputStream的.

 

     FilterInputStream的构造器如下:

     protected  FilterInputStream(InputStream in) 将参数 in 分配给字段 this.in,

     以便记住它供以后使用,通过这种方式创建一个 FilterInputStream。 

 

     注意,这里的构造器是protected的,于是要单独使用FilterInputStream则只能使用

     默认构造器,new FilterInputStream(),而这个构造器根本不能包装任何其他的

     InputStream,所以以便不单独使用这个FilterInputStream,而是使用该类的子类,

     比如DataInputStream等.

 

     DataInputStream的构造器为:

     DataInputStream(InputStream in) 使用指定的底层 InputStream 创建一个 DataInputStream。

 

     因为FilterInputStream的构造函数是protected,Thinking in Java 第4版,p534页中,

     将这个类描述为抽象类了.貌似不准确,但是实际使用时确实不应该实例化该类.

 

2.2 OutputStream

 

    OutputStream 用于向各种目的地写数据.

    OutputStream 也是一个抽象类,主要的方法为 abstract  void write(int b) 将指定的字节写入此输出流。

 

    与InputStream对应的,JDK中同样提供了各种OutputStream的子类用于往不同的目的地写入数据.

    不同子类对于write方法的实现各不相同,使用时需要注意.

 

    需要注意的时,OutputStream的write操作通常将数据写入到缓冲中而不是直接写入到目的地.

    所以,OutputStream还实现了Flushable接口,该接口的 flush()表示将缓冲中的数据写入底层流.

    void flush() 通过将所有已缓冲输出写入底层流来刷新此流。 

 

    常用子类

    (1) ByteArrayOutputStream, 在内存中创建缓冲区,所有送往流中的数据都放在次缓冲区

        构造函数:

        ByteArrayOutputStream() 创建一个新的 byte 数组输出流。

                                缓冲区的容量最初是 32 字节,如有必要可增加其大小。  

        ByteArrayOutputStream(int size) 创建一个新的 byte 数组输出流,

                                        它具有指定大小的缓冲区容量(以字节为单位)。 

        写入数据方法:

        public void write(int b) 将指定的字节写入此 byte 数组输出流.

    (2) FileOutStream 用于将信息写入文件.

        文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。

        文件是否可用或能否可以被创建取决于基础平台。

        特别是某些平台一次只允许一个 FileOutputStream(或其他文件写入对象)

        打开文件进行写入。在这种情况下,如果所涉及的文件已经打开,

        则此类中的构造方法将失败。 

 

        FileOutputStream 用于写入诸如图像数据之类的原始字节的流。

 

        构造函数:

        FileOutputStream(File file) 

          创建一个向指定 File 对象表示的文件中写入数据的文件输出流。 

FileOutputStream(File file, boolean append) 

         创建一个向指定 File 对象表示的文件中写入数据的文件输出流。 

FileOutputStream(FileDescriptor fdObj) 

         创建一个向指定文件描述符处写入数据的输出文件流,

         该文件描述符表示一个到文件系统中的某个实际文件的现有连接。 

FileOutputStream(String name) 

         创建一个向具有指定名称的文件中写入数据的输出文件流。 

FileOutputStream(String name, boolean append) 

         创建一个向具有指定 name 的文件中写入数据的输出文件流。

 (3) PipedOutputStream 与PipedInputStream配套使用的.

     用法参考上文中的PipedInputStream

 (4) FilterOutputStream

     此类与FilterInputStream用法类似,作为用接口DataOutput装饰的比较好用的

     类(DataOutputStream)的子类:

     public class DataOutputStreamextends FilterOutputStreamimplements DataOutput

 

2.3 有用的属性和接口

 

   上文中说过,InputStream,OutputStream提供的主要方法read(),和write()在使用时不够用,不方法.

   于是有了一些方便的接口,如DataInput,这些接口就是用FilterInputStream和FilterOutputStream

   的子类实现的.

 

   FilterInputStream和FilterOutputStream还有一些子类,没有实现任何接口,但是也提供了一种特殊的

   InputStream和OutputStream以方便使用.

 

   这些类包括 DataInputStream(实现了DataInput接口),BufferedInputStream,LineNumberInputStream等

   以及,DataOutputStream,PrintStream,BufferedOutputStream.

 

2.4 InputStream和OutputStream 结构图:

11111111111

3. 以 字符 为导向的 输入输出流 Reader,Writer           

   抽象类Reader,Writer的各种子类已经可以满足不同类似的基于字符的输入和输出.

   尽管也有FilterReader(和FilterInputStream对应)和FilterWriter(与FilterOutputStream对应),

   但是这两个类似乎用处不大.因为Reader,Writer的各种子类已经可以满足各种需求了.

 

   值得注意的是InputStreamReader和OutputStreamWriter这两个适配类把InputStream适配为Reader,

   把OutputStreamWriter适配为Writer.它们俩的直接子类FileReader和FileWriter则可以方便的直接

   对文件进行基于字符的读写.

 

   还需要注意,各种Reader,Writer的子类的构造函数,以便了解如何是用一种子类包装另外的子类,以便

   组合各种Reader,Writer的功能.

   比如,将一个FileReader包装为一个BufferedReader,组成一个带缓冲的File Reader,

   就可以使用下面的代码:

   BufferedReader in = new BufferedReader(new FileReader(filename));

 

   这种包装不影响方法的调用,因为BufferedReader和FileReader的方法几乎是一样的.

 

Reader 和Writer 结构图:

222222222

赞 赏

   微信赞赏  支付宝赞赏


本文固定链接: https://www.jack-yin.com/coding/thinking-in-java/2047.html | 边城网事

该日志由 边城网事 于2015年03月02日发表在 Thinking in Java 分类下, 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
原创文章转载请注明: 第18章 – Java IO 系统 – InputStream,OutputStream,Reader,Writer 总结与实例 | 边城网事
关键字: , , , ,

第18章 – Java IO 系统 – InputStream,OutputStream,Reader,Writer 总结与实例 暂无评论

发表评论

快捷键:Ctrl+Enter