OutputStream类详解

      主要内容包括OutputStream及其部分子类,以分析源代码的方式学习。关心的问题包括:每个字节输出流的作用,各个流之间的主要区别,何时使用某个流,区分节点流和处理流,流的输出目标等问题。 OutputStream的类树如下所示,其中,ObjectOutputStream和PipedOutputStream本文将不做讨论。

java.io.OutputStream (implements java.io.Closeable, java.io.Flushable)
    java.io.ByteArrayOutputStream
    java.io.FileOutputStream
    java.io.FilterOutputStream
        java.io.BufferedOutputStream
        java.io.DataOutputStream (implements java.io.DataOutput)
        java.io.PrintStream (implements java.lang.Appendable, java.io.Closeable)
    java.io.ObjectOutputStream (implements java.io.ObjectOutput, java.io.ObjectStreamConstants)
    java.io.PipedOutputStream
View Code

OutputStream源码分析

package java.io;

//它是抽象类,并且实现了两个接口Closeable和Flushable。
public abstract class OutputStream implements Closeable, Flushable {

    //作为抽象类中唯一的抽象方法,(非抽象)子类必须实现这个方法。
    //我们可以看到,这个类还提供了另外两个write方法,但是它们最终都是要调用这个方法来完成具体的实现
    //对于一个输出流,我们需要关心输出的内容到哪里去了,从这个write方法中我们根本看不到输出的目的地,所以实现这个方法的子类必须告诉这一点
    //而实现这个方法的子类,就是节点流。
    //注意:作为字节输出流,为何这里参数传递为int型,而非byte型,这个在后面子类实现中再分析
    public abstract void write(int b) throws IOException;

    //此方法直接输出一个字节数组中的全部内容,调用了下面的write方法
    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }

    //功能:要输出的内容已存储在了字节数组b[]中,但并非全部输出,只输出从数组off位置开始的len个字节。因此,需要对传入的三个参数作合理性判断
    public void write(byte b[], int off, int len) throws IOException {
        //数组不能为空,否则抛出NullPointerException
        if (b == null) {
            throw new NullPointerException();
        } else if ((off < 0) || (off > b.length) || (len < 0) ||
                   ((off + len) > b.length) || ((off + len) < 0)) {
              //此处判断off+len<0是多余的
              throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        //最终会调用第一个write方法。注意:1.子类可能会复写当前的write方法;2.在输出的过程中,还是一个一个字节输出的。
        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]);
        }
    }

    //这两个方法就是实现两个接口时分别需要实现的方法,但这里方法中内容是空的,子类可以override这两个方法,如果子类不复写,则此方法为空。 
    public void flush() throws IOException { }
    public void close() throws IOException { }

}
View Code

  关于override父类或接口的方法时,原以为要和父类或接口中声明的一样,包括权限,现在看来不然。

package java.io;
import java.io.IOException;
public interface Flushable {
    //此处的方法权限为包权限,而在OutputStream中则成为了public权限    
    void flush() throws IOException;
}

package java.lang;
public interface AutoCloseable {
    //此处的方法权限为包权限,而子接口Closeable中也变成了public权限
    void close() throws Exception;
}

package java.io;
import java.io.IOException;
public interface Closeable extends AutoCloseable {
    public void close() throws IOException;
} 
View Code

ByteArrayOutputStream

package java.io;
import java.util.Arrays;
public class ByteArrayOutputStream extends OutputStream {
    //这里可以回答输出流写到哪里的问题:当我们调用write方法时,把内容都存储到了这个byte数组buf中,且是按照追加的方式添加
    //而count则指向下一个可以写入的位置,它的初始值默认为0
    protected byte buf[];
    protected int count;

    public ByteArrayOutputStream() {
        this(32);
    }
    //类的构造方法只有两个,实际的工作只是在堆中为数组buf申请一块内存,大小可以指定,默认大小为32 
    public ByteArrayOutputStream(int size) {
        if (size < 0) {
            throw new IllegalArgumentException("Negative initial size: "
                                               + size);
        }
        buf = new byte[size];
    }

    //此方法是确保buf的大小不少于minCapacity,如果buf的空间不够,则调用grow()方法来扩展空间。
    private void ensureCapacity(int minCapacity) {
        if (minCapacity - buf.length > 0)
            grow(minCapacity);
    }

    //这个方法的实现值得我们思考一些问题:数组空间不够了,需要扩展,该如何扩展呢?
    //我们可能会这样做:既然你需要minCapacity这么多,那就扩展这么多吧。这里没有这么做,如果这样做,那当用户说我还需要一个字节的空间,那我们就又要在扩展一次,而每一次扩展,都会很耗时。
    //耗时的原因是扩展的方式,本人猜测应该是这么扩展(不确定):重新申请更大的一块内存,然后把原数组的内容拷贝过去。若真如此,那确实会很耗时。
    //这里的策略是:先把原数组的大小通过左移运算扩展为2倍,若这样还不够,那再把大小改为你需要的大小minCapacity。
    //注意:左移运算可能会溢出,使得数组大小变为负数,如果存在溢出,则将其改为Integer.MAX_VALUE。这样的大小是肯定够的,如果这样还不够,那么你传入的minCapacity参数一定有问题
    private void grow(int minCapacity) {
        int oldCapacity = buf.length;
        int newCapacity = oldCapacity << 1;
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity < 0) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        //确定扩展后的数组大小后,通过调用Arrays.copyOf来复制数组,大家可以去研究看是否是先申请更大的一块内存,然后在拷贝。
        buf = Arrays.copyOf(buf, newCapacity);
    }

    //这里实现了父类的抽象方法,从它可以看出,输出流的内容都到了这个类在堆中申请的内存中了,己buf数组。
    //现在也可以回答另外一个问题:对于字节流为何传入int型参数。
    //首先,无论用户传入何种类型参数,我们都强制转换为byte类型。这样可以方便用户,因为它不需要自己实现强制类型转换
    //举例:int a = 10;  write((byte)a);
    //要求用户传入byte类型时,用户需要自己做强制类型转换,但现在我们帮用户做了,岂不方便?
    //这样一来,用户在使用时必须注意这一点:这是字节输出流,如果传入short、char或int等,只把它当作byte处理。
    public synchronized void write(int b) {
        ensureCapacity(count + 1);
        buf[count] = (byte) b;
        count += 1;
    }

    //override了父类的方法,把byte b[]中从off开始的len个字节复制到了buf的后面,同时count增加了len
    public synchronized void write(byte b[], int off, int len) {
        if ((off < 0) || (off > b.length) || (len < 0) ||
            ((off + len) - b.length > 0)) {
            throw new IndexOutOfBoundsException();
        }
        ensureCapacity(count + len);
        System.arraycopy(b, off, buf, count, len);
        count += len;
    }

    //调用此方法,则用户可以把buf中的全部内容输出到用户传入的输出流中
    public synchronized void writeTo(OutputStream out) throws IOException {
        out.write(buf, 0, count);
    }

    public synchronized void reset() {
        count = 0;
    }

    //调用此方法,则用户可以得到一个byte数组,其内容为buf中的全部内容
    public synchronized byte toByteArray()[] {
        return Arrays.copyOf(buf, count);
    }

    public synchronized int size() {
        return count;
    }

    public synchronized String toString() {
        return new String(buf, 0, count);
    }

    public synchronized String toString(String charsetName)
        throws UnsupportedEncodingException
    {
        return new String(buf, 0, count, charsetName);
    }

    @Deprecated
    public synchronized String toString(int hibyte) {
        return new String(buf, hibyte, 0, count);
    }

    //个人认为,此方法既然与父类一样为空,但又写一遍是否多余?为何不像flush方法一样,在这里省去不写
    public void close() throws IOException {
    }
}
View Code

FileOutputStream

  这个类比较复杂,其中还包含nio包中的内容,因此我只看明白了其中一小部分:它是节点流;我们用它来写文件很方便。

package java.io;
import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;
import sun.misc.IoTrace;
public class FileOutputStream extends OutputStream{
    public FileOutputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null, false);
    }

    public FileOutputStream(String name, boolean append)
        throws FileNotFoundException
    {
        this(name != null ? new File(name) : null, append);
    }
    public FileOutputStream(File file) throws FileNotFoundException {
        this(file, false);
    }

    //构造方法一共5个,但实质上只有两个,这是其中一个,另一个是public FileOutputStream(FileDescriptor fdObj),但我都看不懂
    //只说我理解的比较简单的东西:当我们写文件时,我们会选择这个类,原因就是它提供了方法使我们方便地写文件
    //它的构造方法--我们可以直接传入一个File对象,或者代表文件pathName的String,我们就可以指明输出流的目标是哪个文件了
    //其中,append表示是否以追加方式写文件,默认为false,则会覆盖之前文件中的内容
    public FileOutputStream(File file, boolean append)
        throws FileNotFoundException
    {
        ... ...
   }

    //这是必须实现父类的那个方法,我们看不到具体实现,因为它是native方法
    //我们选择这个类操作文件的另一个原因是:这个方法的实现细节一定包含相关的文件操作命令,而其它类不具备这个方法,则不能把流写到文件中 
    private native void write(int b, boolean append) throws IOException;
}
View Code

FilterOutputStream

  它不是节点流,与父类主要差别就是它多了个成员变量。我们一般不会使用这个类,它是另外三个节点输出流的父类。理解它很简单:它什么活也不干,都交给传入的out去做。

package java.io;
public class FilterOutputStream extends OutputStream {
    //此成员变量非常重要,基本上这个类和其父类OutputStream的最主要差别就是它有这个成员变量
    //注意到权限为protected,因此在子类中可以直接使用
    protected OutputStream out;

    //构造方法,传入OutputStream子类对象后,基本上该FilterOutputStream做的事情,它全交给这个传入的对象去做
    public FilterOutputStream(OutputStream out) {
        this.out = out;
    }
    //我们一般从这个方法中就能看到节点输出流的目的地,这里它并没有真正实现,只是调用了传入的out去做,所以FilterOutputStream不是节点流
    public void write(int b) throws IOException {
        out.write(b);
    }
    //表面上调用了下面的write方法,最终还是调用了out的write方法
    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }
    //间接调用out的write方法,以字节为单位地输出
    //这里对传入的参数的判断比较有意思,虽然对参数的要求与OutputStream对应方法对参数的要求一致,但形式确不一样了
    //我的理解:四个量是或的关系,若有一个为负,则最高位必定为1,则最终结果一定为负,因此要求都不能为负
    public void write(byte b[], int off, int len) throws IOException {
        if ((off | len | (b.length - (len + off)) | (off + len)) < 0)
            throw new IndexOutOfBoundsException();

        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]);
        }
    }
    //自己不做,交给out去flush
    public void flush() throws IOException {
        out.flush();
    }
    //自己不做,交给out去close,但是关闭前先调用了flush方法
    public void close() throws IOException {
        try {
          flush();
        } catch (IOException ignored) {
        }
        out.close();
    }
}
View Code

BufferedOutputStream

  它是处理流,有个缓冲数组,能起到缓冲作用,似乎缓冲很有用,详细就不懂了

package java.io;
public class BufferedOutputStream extends FilterOutputStream {  
    //这个类的核心就是这个buf,会将要输出的内容先存在这个数组里,当这个数组满之后再一次全部输出,当然未满是也可以主动输出
    //这个buf似乎与ByteArrayOutputStream有些像,但还是有差别:这个buf大小固定后不会再扩展空间
    protected byte buf[];
    protected int count;
    public BufferedOutputStream(OutputStream out) {
        this(out, 8192);
    }

    //此构造方法需要传入OutputStream实例,可以设置buf大小,默认为8192字节    
    public BufferedOutputStream(OutputStream out, int size) {
        super(out);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }

    //private方法,将buf中缓存的内容全部输出
    private void flushBuffer() throws IOException {
        if (count > 0) {
            out.write(buf, 0, count);
            count = 0;
        }
    }
    //这个写方法根本没有写,只是把要写的内容先存到了buf中。如果buf已经满了,那才会先把buf内容输出,然后再向buf里写
    public synchronized void write(int b) throws IOException {
        if (count >= buf.length) {
            flushBuffer();
        }
        buf[count++] = (byte)b;
    }
    public synchronized void write(byte b[], int off, int len) throws IOException {
        //如果要写入的字节数len比buf的长度还大,那就不需要缓冲了,直接调用out的write方法写就可以
        if (len >= buf.length) {    
            flushBuffer();
            out.write(b, off, len);
            return;
        }
        //如果buf剩余的空间比len小,那就先输出buf内容,腾出空间后再写
        if (len > buf.length - count) {
            flushBuffer();
        }
        System.arraycopy(b, off, buf, count, len);
        count += len;
    }
    //用户需要调用此方法才能实现真正的输出,但是不要每次调用write都紧接着调用flush,那就失去了缓冲的意义了
    //另:在close时,父类FilterOutputStream会调用flush方法的,不用担心,所以你如果调用close的话,该输出的都会输出
    public synchronized void flush() throws IOException {
        flushBuffer();
        out.flush();
    }
}
View Code

DataOutputStream  

  处理流,提供了多个很常用的方法。

package java.io;
public class DataOutputStream extends FilterOutputStream implements DataOutput {
    //这个written参数会不断地累加,但有什么意义没弄明白
    protected int written;
    private byte[] bytearr = null;

    public DataOutputStream(OutputStream out) {
        super(out);
    }

    private void incCount(int value) {
        int temp = written + value;
        if (temp < 0) {
            temp = Integer.MAX_VALUE;
        }
        written = temp;
    }

    public synchronized void write(int b) throws IOException {
        out.write(b);
        incCount(1);
    }

    public synchronized void write(byte b[], int off, int len)
        throws IOException
    {
        out.write(b, off, len);
        incCount(len);
    }

    public void flush() throws IOException {
        out.flush();
    }

    //这个类的核心就是为我们提供了类似writeBoolean这样的方法,我们可以方便地把这些常见类型转为字节并输出,因为这是字节流
    public final void writeBoolean(boolean v) throws IOException {
        out.write(v ? 1 : 0);
        incCount(1);
    }

    //直接输出
    public final void writeByte(int v) throws IOException {
        out.write(v);
        incCount(1);
    }

    //short占两个字节,那么就先把高字节输出,再把低字节输出
    //>>>表示无符号右移,右移8位后在与0xFF做与运算,则可保证此int值的更高位为零,也就是只保留了原int的8-15位
    public final void writeShort(int v) throws IOException {
        out.write((v >>> 8) & 0xFF);
        out.write((v >>> 0) & 0xFF);
        incCount(2);
    }

    //与writeShort完全一致,我的理解是这样在使用时名称很形象
    public final void writeChar(int v) throws IOException {
        out.write((v >>> 8) & 0xFF);
        out.write((v >>> 0) & 0xFF);
        incCount(2);
    }

    //先高字节内容,后低字节
    public final void writeInt(int v) throws IOException {
        out.write((v >>> 24) & 0xFF);
        out.write((v >>> 16) & 0xFF);
        out.write((v >>>  8) & 0xFF);
        out.write((v >>>  0) & 0xFF);
        incCount(4);
    }

    private byte writeBuffer[] = new byte[8];

    //这里没有像之前一个字节一个字节地写,而是先存到writeBuffer中,可能是觉得这样更好,怎么个好法,不懂
    public final void writeLong(long v) throws IOException {
        writeBuffer[0] = (byte)(v >>> 56);
        writeBuffer[1] = (byte)(v >>> 48);
        writeBuffer[2] = (byte)(v >>> 40);
        writeBuffer[3] = (byte)(v >>> 32);
        writeBuffer[4] = (byte)(v >>> 24);
        writeBuffer[5] = (byte)(v >>> 16);
        writeBuffer[6] = (byte)(v >>>  8);
        writeBuffer[7] = (byte)(v >>>  0);
        out.write(writeBuffer, 0, 8);
        incCount(8);
    }

    //float和int型都占用4个字节,因此对float转为对应的int字节流,再调用writeInt
    //Float.floatToIntBits(v)这个方法的实现可能与IEEE规范中关于浮点数规范有关
    public final void writeFloat(float v) throws IOException {
        writeInt(Float.floatToIntBits(v));
    }

    //double和long型都占用8个字节,因此对double转为对应的long字节流,再调用writeLong
    //Double.doubleToLongBits(v)这个方法的实现可能与IEEE规范中关于浮点数规范有关 
    public final void writeDouble(double v) throws IOException {
        writeLong(Double.doubleToLongBits(v));
    }

    //还可以byte处理字符串
    public final void writeBytes(String s) throws IOException {
        int len = s.length();
        for (int i = 0 ; i < len ; i++) {
            out.write((byte)s.charAt(i));
        }
        incCount(len);
    }

    //还可以char处理字符串
    public final void writeChars(String s) throws IOException {
        int len = s.length();
        for (int i = 0 ; i < len ; i++) {
            int v = s.charAt(i);
            out.write((v >>> 8) & 0xFF);
            out.write((v >>> 0) & 0xFF);
        }
        incCount(len * 2);
    }

    public final void writeUTF(String str) throws IOException {
        writeUTF(str, this);
    }

    //可以处理utf-8,这个方法很常用,但其实现还需仔细学习
    static int writeUTF(String str, DataOutput out) throws IOException {
        int strlen = str.length();
        int utflen = 0;
        int c, count = 0;

        /* use charAt instead of copying String to char array */
        for (int i = 0; i < strlen; i++) {
            c = str.charAt(i);
            if ((c >= 0x0001) && (c <= 0x007F)) {
                utflen++;
            } else if (c > 0x07FF) {
                utflen += 3;
            } else {
                utflen += 2;
            }
        }

        if (utflen > 65535)
            throw new UTFDataFormatException(
                "encoded string too long: " + utflen + " bytes");

        byte[] bytearr = null;
        if (out instanceof DataOutputStream) {
            DataOutputStream dos = (DataOutputStream)out;
            if(dos.bytearr == null || (dos.bytearr.length < (utflen+2)))
                dos.bytearr = new byte[(utflen*2) + 2];
            bytearr = dos.bytearr;
        } else {
            bytearr = new byte[utflen+2];
        }

        bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF);
        bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF);

        int i=0;
        for (i=0; i<strlen; i++) {
           c = str.charAt(i);
           if (!((c >= 0x0001) && (c <= 0x007F))) break;
           bytearr[count++] = (byte) c;
        }

        for (;i < strlen; i++){
            c = str.charAt(i);
            if ((c >= 0x0001) && (c <= 0x007F)) {
                bytearr[count++] = (byte) c;

            } else if (c > 0x07FF) {
                bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
                bytearr[count++] = (byte) (0x80 | ((c >>  6) & 0x3F));
                bytearr[count++] = (byte) (0x80 | ((c >>  0) & 0x3F));
            } else {
                bytearr[count++] = (byte) (0xC0 | ((c >>  6) & 0x1F));
                bytearr[count++] = (byte) (0x80 | ((c >>  0) & 0x3F));
            }
        }
        out.write(bytearr, 0, utflen+2);
        return utflen + 2;
    }

    //不知道这个方法有什么用
    public final int size() {
        return written;
    }
}
View Code
posted @ 2016-07-06 16:09  简单爱_wxg  阅读(17862)  评论(0编辑  收藏  举报