Java I/O (2): OutputStream分析

在Java I/O中,抽象类OutputStream是其他输出流类(如FileOutputStream)的基础类,分析一下这个类的源码很有必要。

概要

这个抽象类实现了两个接口:Closeable和Flushable。需要注意的是,在这个类的API中写到这个抽象了实现了三个接口,还包括AutoCloseable,这是因为Closeable接口继承了AutoCloseable接口的缘故。类定义如下:
public abstract class OutputStream implements Closeable, Flushable

该类使用默认的构造函数,没有参数。类中定义了1个抽象方法和4个具体方法,其中close()和flush()是对接口中方法的实现(其实是空实现,什么都没有)。方法声明分别如下:
    public abstract void write(int b) throws IOException;

    public void write(byte b[]) throws IOException

    public void write(byte b[], int off, int len) throws IOException 

以上三个方法都是用于写出字节,具体请看下文。还有两个空实现的方法:
    public void flush() throws IOException {
    }
    public void close() throws IOException {
    }


方法分析

下面是对各个方法的分析,先来看write(int b)

write(int b)

这是唯一一个抽象方法,子类中必须提供具体的实现。这个方法写出一个字节到输出流中。仔细看一下参数,发现是int类型,也就是有4个字节,那是如何处理的呢?很容易想到,只取低8位,也就意味这int的取值范围为0-255.如果提供一个超出这个范围的参数,将自动把高的24位去掉,具体地说,如果给定一个b超出范围,则将b除以256取模。例如,传递一个256,实际上写入的是1。将这个值写入输出流之后,输出流的另一端如何解析,取决于另一端。例如,对于控制台,是将其转换成ASCII码输出。子类必须实现这一方法,例如,FileOutputStream就调用本地方法来实现这个方法。如果写出过程遇到错误,抛出IOException,例如说,这个流已经被关闭,则抛出这个异常。

write(byte b[], int off, int len)

上一个方法一次只写入一个字节,许多时候是不方便的,这个方法则写入data字节数组中的len字节到输出流。参数中data就是待写入的字节(不一定全部写入),从第offset位开始写入,off+len-1是最后一位被写入的。在OutputStream这个抽象类中,直接循环调用write(int b)方法,如下:
if (b == null) {
            throw new NullPointerException();
        } else if ((off < 0) || (off > b.length) || (len < 0) ||
                   ((off + len) > b.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]);
        }

API鼓励子类提供更高性能的实现方法。在这个方法中,由于字节本身就是8位,没有超出范围的情况,所以直接写入无需转换。如果传递的data为空,则抛出NullPointerException异常。如果被写入的部分超出字节数组范围,抛出IndexOutofBoundsException异常。具体地说,有以下5中情况,也就是if语句当中的5个表达式。除此之外,如果写入过程出现错误,一样抛出IOException。

write(byte b[])

这个方法其实是上一个方法的特殊情况,当off=0, len=data.length的时候,就得到了这个方法。具体实现也是通过调用上个方法来实现的,如下:
    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }

有一点需要说明,如果一次写入的字节数组太大,可能就会是性能下降,至于多少合适,得根据具体情况而定。比如写入网络的时候就需要小一点了,如128字节。写入文件的话,稍大一点1024.

flush()

这个方法是对Flushable接口的实现。如果在写出方法的具体实现中,用到了缓冲机制。则这个方法用于将缓冲区的数据“冲刷”到目的地去。这里需要特别理解,如果写出的方法用到了操作系统层的抽象,比如说FileOutputStream,那么该方法只能保证将缓冲区的数据提交给操作系统,但是不能保证数据被写入到磁盘文件中去。如果冲刷过程出现I/O 错误,抛出IOException。OutputStream类中的方法什么都不做。当输出流被关闭或者程序退出的时候,缓冲区的数据互自动被冲刷。对于System.out、System.err这样的输出流,当调用println()方法或者遇到换行符‘\n’的时候,缓冲数据自动被冲刷。当然,你可以在PrintStream的构造函数中传递参数来设置是否自动冲刷。

close()

这是对Closeable接口中close方法的实现。用于关闭输出流,释放相关的系统资源(如文件句柄(file handle)或者网络端口)。关闭之后,该输出流不能再被操作或者重新打开,否则抛出异常。当然,你可以不关闭,但是这不是一个好习惯,时常会出现严重问题。比如你打开文件,正在操作,那你不关闭,其他线程可能就会一直阻塞下去了。可以如下关闭输出流:
try {
  OutputStream out = new FileOutputStream("numbers.dat");
  // Write to the stream...
  out.close( );
}
catch (IOException ex) {
  System.err.println(ex);
}

当然,这样有个问题,如果在写入的时候出现异常,那么输出流的close方法就不会被执行,也就是不会被关闭。换一种方法;
OutputStream out = null;
try {
  out = new FileOutputStream("numbers.dat");
  // Write to the stream...
}
catch (IOException ex) {
  System.err.println(ex);
}
finally {
  if (out != null) {
    try {
      out.close( );
    }
    catch (IOException ex) {
      System.err.println(ex);
    }
  }
}

注意到,这里的OutputStream声明放到了try语句外面,必须这样才能被finally中的语句访问。这样写虽然安全,但是有点丑陋就是了。如果不处理异常而是抛出,那么可以这样:
OutputStream out == null;
try {
  out = new FileOutputStream("numbers.dat");
  // Write to the stream...
}
finally {
  if (out != null) out.close( );
}

一个自定义子类

OutputStream的子类至少要重写write(int b)方法,当然,有些子类把所有方法都重写或者重写一部分。下面这个例子写入的时候什么都不敢,只是提供一个实现参考:
完整代码如下:
import java.io.*;
public class NullOutputStream extends OutputStream {
  private boolean closed = false;
  public void write(int b) throws IOException {
    if (closed) throw new IOException("Write to closed stream");
  }
  public void write(byte[] data, int offset, int length)
   throws IOException {
    if (data == null) throw new NullPointerException("data is null");
    if (closed) throw new IOException("Write to closed stream");
  }
  public void close( ) {
    closed = true;
  }
}

这个类重写了出了flush以外的四个方法,因为写入方法什么都不干,当然没必须flush啦。实现很容易懂。不多说明。




你可能感兴趣的:(java,java,java,IO,IO,IO)