Article

Java I/O

2019-10-18 Java

InputStream

闲话:java.io下的类数量繁多,记得第一次试图尝试研究一下java io源码时,无从下手,最近在接触了一些设计模式后在重新梳理研究一下Java I/O, 以此记录.

  • 描述

    InputStream: This abstract class is the superclass of all classes representing an input stream of bytes

    输入源 对应的具体类 构造方法参数及说明
    字节数组 ByteArrayInputStream 一个byte[]数组
    String StringBufferedInputStream 以弃用
    文件 FileInputStream String, File, FileDescriptor, 指从一个文件中读取信息
    管道 PipedInputStream 可以想象成实际中的管道,你把一些东西从管子一头放入,而他们将从另一头出来
    其他输入流的组合序列 SequenceInputStream 用来表示逻辑的串联流(指任意其他InputStream)
    其他数据源 网络等  
    抽象装饰器 FilterInputStream 所有具体装饰器的父类
  • InputStream

    日常生活中当我们需要水时,往往有许多途径可以获得;如自来水(即从水管中),瓶装水(各种饮料),小溪,水库,等等,而InputStream则相当于所有可以获得水资源的途径,我们都可以从这些途径中取得水资源(即InputStream中的read()方法).而从每个途径中获取水资源的方法尽不相同,所以这个read()方法是抽象的,需要子类去具体实现(如自来水从水龙头出水, 矿泉水从瓶口出水,小溪可以沿途取水等等)

    package java.io;
    public abstract class InputStream implements Closeable {
    
      private static final int MAX_SKIP_BUFFER_SIZE = 2048;
    
      //这是所有子类必须实现的方法,如果在学Java时习惯了System.in, System.out
      //时,一开始时很难进入到一个抽象的思维中的(对我来说),所以一开始并没有理解这个
      //read().
      //目前理解整理一下:
      //InputStream其实现类代表一个可以读的*数据载体*, 当需要从中读入一些信息(如字节,字符)时
      //调用在其上的read()方法,这个方法返回一个int类型的数据
      //  byte[] buf = new byte[]{0x11, 0x23};
      //  ByteArrayInputStream in = new ByteArrayInputStream(buf);
      //  System.out.println(in.read());
      //  其输出结果回返回17(0x11)
      //而在上面这个buf就是作为一种数据载体,被抽象成为一个数据流,并从其中读取一个字节
      public abstract int read() throws IOException;
    
      /**
       * 传入一个字节数组b,将尽可能读到的字节缓冲到b中
       * 这个函数最终会调用read(b, 0, b.length)作为一般的情况来使用
       */
      public int read(byte b[]) throws IOException {
          return read(b, 0, b.length);
      }
      /**
       * 这个方法蛮清晰的, 分几种情况判断
       * 1.b为null,抛出NullPointerException
       * 2.off<0, len<0, len>b.length属于取值范围错误,并抛出IndexOutOfBoundesException
       * 3.len == 0, 直接返回0作为一种正确示意(不同于-1)
       * 接下来就是从给定的数据载体中逐个读取字节,即每个具体的实现类表达的一种抽象流:
       * 如ByteArrayInputStream表示从给定的byte[]中取得数据,直至到达末尾
       * FileInputStream表示从文件中读取数据,
       * 每个具体的实现类实现相应的read()抽象方法
       * 注意:这个方法会阻塞直到可以读取数据
       */
      public int read(byte b[], int off, int len) throws IOException {
          if (b == null) {
              throw new NullPointerException();
          } else if (off < 0 || len < 0 || len > b.length - off) {
              throw new IndexOutOfBoundsException();
          } else if (len == 0) {
              return 0;
          }
    
          int c = read();
          if (c == -1) {
              return -1;
          }
          b[off] = (byte)c;
    
          int i = 1;
          try {
              for (; i < len ; i++) {
                  c = read();
                  if (c == -1) {
                      break;
                  }
                  b[off + i] = (byte)c;
              }
          } catch (IOException ee) {
          }
          return i;
      }
    
      /**
       * 从输入流中跳过并丢弃给定长度n的字节
       * 有多种特殊情况导致skip停止
       * 1. n <= 0;(直接返回0, 不跳过任何字节, 并且默认方法不处理负数,由部分实现类处理相应逻辑)
       * 2. 在跳过n字节之前到达输入流末尾(返回跳过的的字节数量)
       * 内部创建一个byte[]数组用来保存跳过的数据直到跳过n字节或到达输入流末尾
       */
      public long skip(long n) throws IOException {
    
          long remaining = n;
          int nr;
    
          if (n <= 0) {
              return 0;
          }
    
          int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
          byte[] skipBuffer = new byte[size];
          while (remaining > 0) {
              nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
              if (nr < 0) {
                  break;
              }
              remaining -= nr;
          }
    
          return n - remaining;
      }
    
      public int available() throws IOException {
          return 0;
      }
    
      public void close() throws IOException {}
    
      public synchronized void mark(int readlimit) {}
    
      public synchronized void reset() throws IOException {
          throw new IOException("mark/reset not supported");
      }
    
      public boolean markSupported() {
          return false;
      }
    }
    
  • FileInputStream

    可以初略想象称这是一个矿泉水,你传给他的文件就是你想要储存在瓶里的水资源,当你要喝水时,则可以打开瓶盖喝水:即调用相应的read()方法,喝完水时盖紧瓶盖:即调用close().

    简单使用

      public static void main(String[] args) throws IOException
      {
          FileInputStream in = new FileInputStream("test");
          while(in.available() != 0)
              System.out.print(in.read() + " ");
          System.out.println();
    
          DataInputStream dataIn = new DataInputStream(new FileInputStream("test"));
          while(dataIn.available() != 0)
              System.out.print(dataIn.readLine());
          in.close();
          dataIn.close();
      }
      /**
       * output:
       * 105 116 32 105 115 32 97 32 115 105 109 112 108 101 32 102 105 108 101 41
       * it is a simple file.
    

    分析: test是我在当前目录创建的一个简单文本文件, 里面包含了’it is a simple file.’,当我将他传给FileInputStream时便可以对该文件进行读操作了,不过我们可以注意到,单纯的一个FileInputStream从中读到的数据时原始的字节数据,即这些字符相对应的ASCII码值,所以下面用一个DataInputStream来装饰它,使他能够拥有更多的功能,获得你想要的结果;

    综上: FileInputStream其本身也是一个抽象表示,我们很少直接使用它,当传递一个文件名或一个File对象时,FileInputStream的负责链接到这个真实的文件,我们只管利用它提供的接口进行读即可, 而他本身提供的接口有限且往往不符合我们的期待,所以Java I/O设计者采用装饰者模式,动态的扩展其上的功能达到我们想要的功能.

  • ByteArrayInputStream

    这个类很简单, 可直接阅读源代码, 构造函数传入一个byte[]数组, 另外在这个类上面调用close是无任何实际效应的.为什么在下方解释

    简单使用

      public static void main(String[] args) throws IOException 
      {
    
          String source = "hello world";
          //通过String的getBytes()返回一个byte[]数组并构造一个ByteArrayInputStream
          ByteArrayInputStream in = new ByteArrayInputStream(source.getBytes());
          int c;
          while ((c = in.read()) != -1)
              System.out.print((char) c);
          in.close();//close无效
          System.out.println();
    
          //将内部指针重设至初始位置
          in.reset();
          System.out.println((char)in.read());
    
          //给mark传入的参数是无意义的
          // public void mark(int readAheadLimit){
          //    mark = pos;
          //}
          in.mark(3);
          in.reset();
          System.out.println((char)in.read());
      }
      /**
       * output:
       * hello world
       * h
       * e
       */
    

    分析: ByteArrayInputStream这个类就是代表在一个字节数组上进入读操作,其源码也非常简单易懂,之说以在其上调用close()无效果, 因为这个InputStream并不涉及像File, Pipe,等与操作系统息息相关的操作,就是一个简单的从给定的byte[]数组中读取数据,而一个byte[]数组就没有关闭的概念了,就像我们可以反复从一个数组里通过下标取得数据一样

  • SequenceInputStream

    组个多个流为单个流,这个类也很简单,其够着函数有两个 |构造函数|说明| |-|-| |SequenceInputStream(Enumeration<T extends InputStream> e)| |SequenceInputStream(InputStream s1, InputStream s2)|其最终也是创建一个含有两个InputStream的Enumeration

    简单使用

      public static void main(String[] args) throws IOException
      {
          FileInputStream fileIn = new FileInputStream("test");
          ByteArrayInputStream baIn = new ByteArrayInputStream("byte array".getBytes());
    
          SequenceInputStream sIn = new SequenceInputStream(fileIn, baIn);
          int c;
          while((c = sIn.read()) != -1)
              System.out.print((char)c);
          fileIn.close();
          baIn.close();
          sIn.close();
      }
      /**
       * output:
       * it is a simple file.byte array