OutputStream分析

来源:互联网 发布:淘宝首饰店 编辑:程序博客网 时间:2024/05/08 17:07

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

概要

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

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

[java] view plaincopy
  1. public void write(byte b[]) throws IOException  

[java] view plaincopy
  1. public void write(byte b[], int off, int len) throws IOException   

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


方法分析

下面是对各个方法的分析,先来看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)方法,如下:
[java] view plaincopy
  1. if (b == null) {  
  2.             throw new NullPointerException();  
  3.         } else if ((off < 0) || (off > b.length) || (len < 0) ||  
  4.                    ((off + len) > b.length) || ((off + len) < 0)) {  
  5.             throw new IndexOutOfBoundsException();  
  6.         } else if (len == 0) {  
  7.             return;  
  8.         }  
  9.         for (int i = 0 ; i < len ; i++) {  
  10.             write(b[off + i]);  
  11.         }  

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

write(byte b[])

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

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

flush()

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

close()

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

当然,这样有个问题,如果在写入的时候出现异常,那么输出流的close方法就不会被执行,也就是不会被关闭。换一种方法;
[java] view plaincopy
  1. OutputStream out = null;  
  2. try {  
  3.   out = new FileOutputStream("numbers.dat");  
  4.   // Write to the stream...  
  5. }  
  6. catch (IOException ex) {  
  7.   System.err.println(ex);  
  8. }  
  9. finally {  
  10.   if (out != null) {  
  11.     try {  
  12.       out.close( );  
  13.     }  
  14.     catch (IOException ex) {  
  15.       System.err.println(ex);  
  16.     }  
  17.   }  
  18. }  

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

一个自定义子类

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

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