Java I/O源碼分析 - InputStream,OutputStream系列

說明

整個系列的文章全部參考或直接照搬下面兩位作者的文章,這里只是根據(jù)自己需要對原作者的文章梳理的總結(jié),僅給自己日后復(fù)習時提供思路,如有讀者看到學習時建議移步原作。再次重申并非我所寫

另一篇本人總結(jié)的IO系列

HikariCP:Java IO源碼分析 - Reader,Writer系列(一)

HikariCP:Java IO源碼分析 - Reader,Writer系列(二)

IntputStream,OutputStream 簡介

  • 所有字節(jié)輸入流的類的父類 IntputStream。
  • 所有字節(jié)輸出流的類的父類 OutputStream。

助于理解

無論是輸入流還是輸出流,都是相對于內(nèi)存的,即內(nèi)存數(shù)據(jù)的輸入還是輸出,所以InputStream就是往內(nèi)存輸入數(shù)據(jù)的輸入流。對于內(nèi)存的動作就是read讀取。相對的OutputStream就是從內(nèi)存中往外輸出數(shù)據(jù),對于內(nèi)存的動作的就是write操作。

public abstract class InputStream implements Closeable {
}

public abstract class OutputStream implements Closeable, Flushable {
}

所有字節(jié)輸入流的父類 InputStream 有這樣一個抽象方法:

public abstract int read() throws IOException;

所以字節(jié)輸入流必須提供返回下一個輸入字節(jié)的read()方法。

ByteArrayInputStream

  • ByteArrayInputStream 支持 mark/reset。
  • ByteArrayInputStream的close方法無效,無法關(guān)閉此輸入流。
public void mark(int readAheadLimit) {
    // 設(shè)置流中的當前標記位置
    mark = pos;
}

public synchronized void reset() {
    // 將緩沖區(qū)的位置重置為標記位置
    pos = mark;
}

public void close() throws IOException {
}

實現(xiàn)了父類InputStream的read方法。

/*
 * 返回一個 0 到 255 范圍內(nèi)的 int 字節(jié)值。
 * 負數(shù)用補碼進行計算
 */
public synchronized int read() {
    return (pos < count) ? (buf[pos++] & 0xff) : -1;
}

需要注意的是,如果buf數(shù)組中有負數(shù)的話,負數(shù)在取出時&與運算用負數(shù)的補碼(除符號位全部取反并+1)進行計算。

ByteArrayOutputStream

public class ByteArrayOutputStream extends OutputStream {

/**
 * The buffer where data is stored.
 * 存儲數(shù)據(jù)的緩沖區(qū)
 */
protected byte buf[];

/**
 * The number of valid bytes in the buffer.
 * 緩沖區(qū)中的有效字節(jié)數(shù)
 */
protected int count;
}

// 緩沖區(qū)容量初始化為32,如有必要可擴容。通過ensureCapacity
public ByteArrayOutputStream() {
    this(32);
}

public ByteArrayOutputStream(int size) {
    if (size < 0) {
        throw new IllegalArgumentException("Negative initial size: "
                                           + size);
    }
    buf = new byte[size];
}

ensureCapacity,grow,hugeCapacity

// 確保緩沖區(qū)可以存放多少元素,必要時擴容
private void ensureCapacity(int minCapacity) {
    // overflow-conscious code
    if (minCapacity - buf.length > 0)
        grow(minCapacity);
}

// 增加緩沖區(qū)容量,使其至少可以存放minCapacity個元素
// minCapacity : 期望最小容量
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = buf.length;
    // 擴容2倍
    int newCapacity = oldCapacity << 1;
    if (newCapacity - minCapacity < 0)
        // 如果擴容兩倍還是小,那么容量賦值成該期望容量
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        // 如果還是不夠,那么最大提升到 Integer.MAX_VALUE 舍棄到了頭信息
        newCapacity = hugeCapacity(minCapacity);
    buf = Arrays.copyOf(buf, newCapacity);
}

// 計算允許分配給byte數(shù)組的最大容量
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

// 一些虛擬機會存一些頭信息到數(shù)組中,如數(shù)組的地址類型等,提升性能
// JVM默認規(guī)定數(shù)組最大容量就是Integer.MAX_VALUE,再打會內(nèi)存溢出
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

write,writeTo

// 將指定的字節(jié)寫入輸出流
public synchronized void write(int b) {
    ensureCapacity(count + 1);
    buf[count] = (byte) b;
    count += 1;
}

implement了父類OutputStream抽象類的write方法
public abstract void write(int b) throws IOException;

// 將指定byte數(shù)組中從偏移量off開始的len個字節(jié)寫入輸出流
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;
}

// 將此byte數(shù)組輸出流的全部內(nèi)容寫入到指定的輸出流參數(shù)out中
public synchronized void writeTo(OutputStream out) throws IOException {
    out.write(buf, 0, count);
}

重要函數(shù)

// 將輸出流的count字段重置為零,從而丟棄輸出流中目前已累積的所有輸出
public synchronized void reset() {
    count = 0;
}

// 使用指定的charsetName,通過解碼字節(jié)將緩沖區(qū)內(nèi)容轉(zhuǎn)換為字符串并返回
public synchronized String toString(String charsetName) throws UnsupportedEncodingException {
    return new String(buf, 0, count, charsetName);
}

public void close() throws IOException {
}

總結(jié)

  • ByteArrayOutputStream中的數(shù)據(jù)被寫入到一個byte數(shù)組里。byte數(shù)組會隨著被寫入其中的數(shù)據(jù)的增長而增長。
  • 表示字節(jié)輸出流的類必須提供至少一種可寫入一個輸出字節(jié)的方法。ByteArrayOutputStream提供了兩種。加上繼承自父類OuputStream類的write方法是3種
  • ByteArrayOutputStream可以將緩沖區(qū)中的數(shù)據(jù)轉(zhuǎn)化為byte數(shù)組或字符串并返回。
  • ByteArrayOutputStream可以通過writeTo( OutputStream out)實現(xiàn)輸出流之間數(shù)據(jù)的復(fù)制
  • ByteArrayOutputStream 的close方法無效,無法關(guān)閉此輸出流。

PipedInputStream,PipedOutputStream

PipedInputStream與PipedOutputStream分別為管道輸入流和管道輸出流。管道輸入流通過連接到管道輸出流實現(xiàn)了類似管道的功能,用于線程之間的通信。

通常,由某個線程向管道輸出流中寫入數(shù)據(jù)。根據(jù)管道的特性,這些數(shù)據(jù)會自動發(fā)送到與管道輸出流對應(yīng)的管道輸入流中。這時其他線程就可以從管道輸入流中讀取數(shù)據(jù),這樣就實現(xiàn)了線程之間的通信。

public class PipedInputStream extends InputStream

initPipe,connect,

/**
 * 初始化PipedInputStream的緩沖區(qū)大小
 *
 * @param pipeSize 管道緩沖區(qū)容量
 */
private void initPipe(int pipeSize) {
    if (pipeSize <= 0) {
        throw new IllegalArgumentException("Pipe Size <= 0");
    }
    buffer = new byte[pipeSize];
}

/**
 * 將PipedInputStream連接到指定的PipedOutputStream。
 *
 * 如果 PipedInputStream 已經(jīng)被連接到了其他 PipedOutputStream,
 * 或者PipedOutputStream 已經(jīng)被連接到其他PipedInputStream 
 * 拋出IOException。
 */
public void connect(PipedOutputStream src) throws IOException {
    src.connect(this);
}

receive,awaitSpace,checkStateForReceive

// 接收一個數(shù)據(jù)字節(jié),將其插入到緩沖區(qū)。如果沒有可用的輸入,方法會阻塞
protected synchronized void receive(int b) throws IOException {
    // 接受前的狀態(tài)檢查
    checkStateForReceive();
    // 設(shè)置負責向管道緩沖區(qū)輸入數(shù)據(jù)的線程是當前線程
    writeSide = Thread.currentThread();
    // 如果緩沖區(qū)被塞滿的時候
    if (in == out)
        // 緩沖區(qū)被寫入線程塞滿的時候,喚醒讀取線程,并阻塞當前寫入線程
        awaitSpace();
    // 初次接受前初始化
    if (in < 0) {
        in = 0;
        out = 0;
    }
    buffer[in++] = (byte) (b & 0xFF);
    // 從頭復(fù)入
    if (in >= buffer.length) {
        in = 0;
    }
}

// 檢查PipedInputStream是否可以接收數(shù)據(jù)
private void checkStateForReceive() throws IOException {
    if (!connected) {
        throw new IOException("Pipe not connected");
    } else if (closedByWriter || closedByReader) {// 輸入輸出流都不能被關(guān)閉
        throw new IOException("Pipe closed");
    } else if (readSide != null && !readSide.isAlive()) {// switSpace 需要用到讀線程,讀線程不能為空且不alive
        throw new IOException("Read end dead");
    }
}

// 等待可用緩沖區(qū)
private void awaitSpace() throws IOException {
    while (in == out) {
        checkStateForReceive();

        /* full: kick any waiting readers */
        notifyAll();
        try {
            wait(1000);
        } catch (InterruptedException ex) {
            throw new java.io.InterruptedIOException();
        }
    }
}

// 接受指定字節(jié)數(shù)組的數(shù)據(jù)
synchronized void receive(byte b[], int off, int len) throws IOException {
    // 檢查接受狀態(tài)
    checkStateForReceive();
    // 身份
    writeSide = Thread.currentThread();
    // 寫入總量
    int bytesToTransfer = len;
    while (bytesToTransfer > 0) {
        // 判斷緩沖區(qū)滿沒滿
        if (in == out)
            awaitSpace();
        // 下次插入量
        int nextTransferAmount = 0;
        if (out < in) {
            nextTransferAmount = buffer.length - in;
        } else if (in < out) {
            // 初次寫入
            if (in == -1) {
                in = out = 0;
                nextTransferAmount = buffer.length - in;
            } else {
                // 再次寫入
                nextTransferAmount = out - in;
            }
        }
        // 如果 可插入的量 > 要插入的量,那么一次插入結(jié)束,此次插入的量就是寫入線程要插入數(shù)據(jù)的總量,
        // 否則off記錄偏移量,in記錄存入位,bytesToTransfer記錄剩余插入量,while循環(huán)批次執(zhí)行。
        if (nextTransferAmount > bytesToTransfer)
            nextTransferAmount = bytesToTransfer;
        assert (nextTransferAmount > 0);
        // 寫入
        System.arraycopy(b, off, buffer, in, nextTransferAmount);
        bytesToTransfer -= nextTransferAmount;
        off += nextTransferAmount;
        in += nextTransferAmount;
        if (in >= buffer.length) {
            in = 0;
        }
    }
}

receivedLast

// 管道輸出流關(guān)閉時(PipedOutputStream.close()中會調(diào)用此方法),通知其已經(jīng)關(guān)閉。
synchronized void receivedLast() {
    // 狀態(tài)設(shè)置
    closedByWriter = true;
    notifyAll();
}

read

public synchronized int read() throws IOException {
    // 狀態(tài)檢測
    if (!connected) {// 輸入輸出流是否連接上
        throw new IOException("Pipe not connected");
    } else if (closedByReader) {// 管道輸入流被關(guān)閉
        throw new IOException("Pipe closed");
    } else if (writeSide != null && !writeSide.isAlive() // 寫線程存在但不alive,管道輸出流沒關(guān) 且現(xiàn)在管道中沒數(shù)據(jù)
            && !closedByWriter && (in < 0)) {
        throw new IOException("Write end dead");
    }

    // 狀態(tài)設(shè)置,當前線程為讀取線程
    readSide = Thread.currentThread();
    // 嘗試次數(shù)
    int trials = 2;
    // 管道中沒數(shù)據(jù)
    while (in < 0) {
        // 如果管道輸出流關(guān)閉,且此時in<0 管道緩沖區(qū)沒機會再寫入內(nèi)容了,read 返回 return -1
        if (closedByWriter) {
            /* closed by writer, return EOF */
            return -1;
        }
        // 如果寫入數(shù)據(jù)的線程不為null且不活躍且trials<=0,說明管道損壞,拋出異常
        if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {
            throw new IOException("Pipe broken");
        }
        /* might be a writer waiting */ // 喚醒寫入
        notifyAll();
        try {
            wait(1000);
        } catch (InterruptedException ex) {
            throw new java.io.InterruptedIOException();
        }
    }
    int ret = buffer[out++] & 0xFF;
    // 讀完一輪,復(fù)位
    if (out >= buffer.length) {
        out = 0;
    }
    // 管道緩沖區(qū)為空,讀完
    if (in == out) {
        /* now empty */
        in = -1;
    }

    return ret;
}

public synchronized 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;
    }

    /* possibly wait on the first character */
    // 嘗試讀一個字節(jié),看緩沖區(qū)情況
    int c = read();
    if (c < 0) {
        return -1;
    }
    b[off] = (byte) c;
    // readLength
    int rlen = 1;
    while ((in >= 0) && (len > 1)) {

        int available;

        if (in > out) {
            // 可讀數(shù)
            available = Math.min((buffer.length - out), (in - out));
        } else {
            available = buffer.length - out;
        }

        // A byte is read beforehand outside the loop
        // 可讀數(shù) > 要讀數(shù)
        if (available > (len - 1)) {
            available = len - 1;
        }
        System.arraycopy(buffer, out, b, off + rlen, available);
        // 一次讀取影響到的變量統(tǒng)一變更
        out += available;
        rlen += available;
        len -= available;

        // 繼續(xù)從頭讀
        if (out >= buffer.length) {
            out = 0;
        }
        // 緩沖區(qū)讀完
        if (in == out) {
            /* now empty */
            in = -1;
        }
    }
    return rlen;
}

available,close

public synchronized int available() throws IOException {
    if (in < 0)
        return 0;
    else if (in == out)// 讀完被置為-1 所以這里肯定是滿了
        return buffer.length;
    else if (in > out)
        return in - out;
    else
        return in + buffer.length - out;
}

/**
 * 關(guān)閉此管道輸入流,并釋放與該流相關(guān)的所有系統(tǒng)資源。
 */
public void close()  throws IOException {
    // 狀態(tài)設(shè)置
    closedByReader = true;
    synchronized (this) {
        in = -1;// 
    }
}

PipedOutputStream

public class PipedOutputStream extends OutputStream {
    
    private PipedInputStream sink; // 與PipedOutputStream相連接的管道輸入流
    
    // 創(chuàng)建連接到指定輸入流的管道輸出流
    public PipedOutputStream(PipedInputStream snk)  throws IOException {
        connect(snk);
    }
    
    // 創(chuàng)建沒有連接到輸入流的管道輸出流。
    // 在使用前,它必須連接到管道輸入流。
    public PipedOutputStream() {
    }
}

connect,write

public synchronized void connect(PipedInputStream snk) throws IOException {
    if (snk == null) {
        throw new NullPointerException();
    } else if (sink != null || snk.connected) {// 此管道輸出流已經(jīng)與某管道輸入流連接 或 該管道輸入流已經(jīng)被連接
        throw new IOException("Already connected");
    }
    sink = snk;
    snk.in = -1;
    snk.out = 0;
    snk.connected = true;
}

// 將指定數(shù)據(jù)字節(jié)寫入管道輸出流
public void write(int b)  throws IOException {
    if (sink == null) {
        throw new IOException("Pipe not connected");
    }
    sink.receive(b);
}

// 將指定字節(jié)數(shù)組的數(shù)組寫入到管道緩沖區(qū)中
public void write(byte b[], int off, int len) throws IOException {
    // 數(shù)據(jù)校驗
    if (sink == null) {
        throw new IOException("Pipe not connected");
    } else 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;
    }
    // 調(diào)用管道輸入流的receive函數(shù)處理
    sink.receive(b, off, len);
}

close,receivedLast,flush

public void close()  throws IOException {
    if (sink != null) {
        sink.receivedLast();
    }
}

// PipedInputStream類的receivedLast函數(shù)
synchronized void receivedLast() {
    // 狀態(tài)設(shè)置,負責緩沖區(qū)數(shù)據(jù)寫入的流被關(guān)閉了。
    closedByWriter = true;
    notifyAll();
}

/**
 * 刷新此輸出流并強制寫出所有緩沖的輸出字節(jié)。
 * 這將通知所有讀取數(shù)據(jù)的線程,告知它們管道中的字符處于讀取等待中。
 */
public synchronized void flush() throws IOException {
    if (sink != null) {
        synchronized (sink) {
            sink.notifyAll();
        }
    }
}

總結(jié)

首先在看PipedInputStream和PipedOutputStream的時候,我剛開始沒搞懂為什么PipedInputStream技能read又能recieve。后來看了PipedOutputStream的源碼的時候才知道,原來PipedInputStream類中的recieve函數(shù)是給PipedOutputStream類中的write函數(shù)調(diào)用的,后來才串明白,這是一個“管道”的輸入輸出流,用一個容量默認為1024的byte數(shù)組來做管道的緩沖容量,最終的輸入輸出實現(xiàn)都落實到了PipedInputStream類中,這樣狀態(tài)都由一個類來控制才能做到,某個線程通過管道輸出流向管道中寫入數(shù)據(jù),另一端管道輸入流能立馬從管道中取出對應(yīng)存儲到的數(shù)據(jù)。

  • PipedInputStream與PipedOutputStream分別為管道輸入流和管道輸出流。管道輸入流通過連接到管道輸出流實現(xiàn)了類似管道的功能,用于線程之間的通信。
  • 通常,由某個線程向管道輸出流中寫入數(shù)據(jù)。根據(jù)管道的特性,這些數(shù)據(jù)會自動發(fā)送到與管道輸出流對應(yīng)的管道輸入流中。這時其他線程就可以從管道輸入流中讀取數(shù)據(jù),這樣就實現(xiàn)了線程之間的通信。
  • 不建議對這兩個流對象嘗試使用單個線程,因為這樣可能死鎖線程。
  • PipedOutputStream是數(shù)據(jù)的發(fā)送者;PipedInputStream是數(shù)據(jù)的接收者。
  • PipedInputStream緩沖區(qū)大小默認為1024,寫入數(shù)據(jù)時寫入到這個緩沖區(qū)的,讀取數(shù)據(jù)也是從這個緩沖區(qū)中讀取的。
  • PipedInputStream通過read方法讀取數(shù)據(jù)。PipedOutputStream通過write方法寫入數(shù)據(jù),write方法其實是調(diào)用PipedInputStream中的receive方法來實現(xiàn)將數(shù)據(jù)寫入緩沖區(qū)的的,因為緩沖區(qū)是在PipedInputStream中的。
  • PipedOutputStream和PipedInputStream之間通過connect()方法連接。
  • 使用后要關(guān)閉輸入輸出流

FilterInputStream,F(xiàn)ilterOutputStream

FilterInputStream、FilterOutputStream是過濾器字節(jié)輸入輸出流。它們的主要用途在于封裝其他的輸入輸出流,為它們提供一些額外的功能。 - 裝飾者模式

package java.io;

public class FilterInputStream extends InputStream {
    protected volatile InputStream in;

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

    public int read() throws IOException {
        return in.read();
    }

    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    public int read(byte b[], int off, int len) throws IOException {
        return in.read(b, off, len);
    }

    public long skip(long n) throws IOException {
        return in.skip(n);
    }

    public int available() throws IOException {
        return in.available();
    }

    public void close() throws IOException {
        in.close();
    }

    public synchronized void mark(int readlimit) {
        in.mark(readlimit);
    }

    public synchronized void reset() throws IOException {
        in.reset();
    }

    public boolean markSupported() {
        return in.markSupported();
    }
}

可以從源碼看出,F(xiàn)ilterInuptStream類本身并沒有對構(gòu)造時傳入的InputStream抽象類的實例進行裝飾,只是簡單的重寫了父類InputStream類的所有方法。

可以看出FilterInputStream在這里做的是裝飾抽象類。而InputStream做的是抽象構(gòu)建。

根據(jù)裝飾模式的設(shè)計思想我們可以得知,雖然FilterInutStream類并不為具體構(gòu)建提供裝飾功能。但其子類在裝飾模式中充當?shù)氖蔷唧w裝飾類,可以進一步重寫這些方法中的一些方法,來提供裝飾功能。它的常用子類有BufferedInputStreamDataInputStream。比如,BufferedInputStream的作用就是為它裝飾的輸入流提供緩沖功能。

至此:

  • InputStream:抽象構(gòu)建
  • ***InputStream:具體構(gòu)建
  • FilterInputStream:抽象裝飾類
  • BufferedInputStream:具體裝飾類

filterOutputStream

public class FilterOutputStream extends OutputStream {
 
    protected OutputStream out;

    public FilterOutputStream(OutputStream out) {
        this.out = out;
    }

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

    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }

    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]);
        }
    }

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

    @SuppressWarnings("try")
    public void close() throws IOException {
        try (OutputStream ostream = out) {
            flush();
        }
    }
}

需要注意的是看了FilterInputStream源碼后不要想當然的認為FilterOutputStream也和它一樣全篇方法調(diào)用裝飾的OutputStream的子類的原方法。其也重寫的父類的OutputStream全部方法,并有一些賦予了自己的處理邏輯。

總結(jié)

  • FilterInputStream、FilterOutputStream是過濾器字節(jié)輸入輸出流。它們的主要用途在于封裝其他的輸入輸出流,為它們提供一些額外的功能。
  • FilterInputStream、FilterOutputStream并沒有提供什么裝飾功能。FilterInputStream、FilterOutputStream的子類可進一步重寫這些方法中的一些方法,來提供裝飾功能。
  • FilterInputStream裝飾功能的實現(xiàn)的關(guān)鍵在于類中有一個InputStream字段,依賴這個字段它才可以對InputStream的子類提供裝飾功能。FilterOutputStream也是如此。

BufferedInputStream,BufferedOutputStream

public class BufferedInputStream extends FilterInputStream {



    private static int DEFAULT_BUFFER_SIZE = 8192;// 1024 << 3; 緩沖區(qū)默認的默認大小
    private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;// 分派給arrays的最大容量
    protected volatile byte buf[];// 存放數(shù)據(jù)的內(nèi)部緩沖數(shù)組。,如果有必要,它可能被不同大小的數(shù)組替代
    // 當前緩沖區(qū)的有效字節(jié)數(shù)。
    // 注意,這里是指緩沖區(qū)的有效字節(jié)數(shù),而不是輸入流中的有效字節(jié)數(shù)。
    protected int count;
    // 當前緩沖區(qū)的位置索引
    // 注意,這里是指緩沖區(qū)的位置索引,而不是輸入流中的位置索引。
    protected int pos;
    // 當前緩沖區(qū)的標記位置
    // markpos和reset()配合使用才有意義。操作步驟:
    // (01) 通過mark() 函數(shù),保存pos的值到markpos中。
    // (02) 通過reset() 函數(shù),會將pos的值重置為markpos。接著通過read()讀取數(shù)據(jù)時,就會從mark()保存的位置開始讀取。
    // 可以理解為,mark位置之后的數(shù)據(jù)是保留數(shù)據(jù),即有效數(shù)據(jù)。mark確立了有效數(shù)據(jù)和無效數(shù)據(jù)。
    protected int markpos = -1;
    // 相當于從輸入流中一次讀取數(shù)據(jù)的大小。當buffer.length小于這個值的時候就需要頻繁的擴容,當大于這個值的時候就可以直接從輸入流中讀取數(shù)據(jù)。
    protected int marklimit;
     // 緩存數(shù)組的原子更新器。
     // 該成員變量與buf數(shù)組的volatile關(guān)鍵字共同組成了buf數(shù)組的原子更新功能實現(xiàn),
     // 即,在多線程中操作BufferedInputStream對象時,buf和bufUpdater都具有原子性(不同的線程訪問到的數(shù)據(jù)都是相同的)
    private static final
        AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
        AtomicReferenceFieldUpdater.newUpdater
        (BufferedInputStream.class,  byte[].class, "buf");
}

BufferedInputStream的作用是為其它輸入流提供緩沖功能。創(chuàng)建BufferedInputStream時,我們會通過它的構(gòu)造函數(shù)指定某個輸入流為參數(shù)。BufferedInputStream會將該輸入流數(shù)據(jù)分批讀取,每次讀取一部分到緩沖中;操作完緩沖中的這部分數(shù)據(jù)之后,再從輸入流中讀取下一部分的數(shù)據(jù)。(即對應(yīng)read時發(fā)現(xiàn)緩沖區(qū)數(shù)據(jù)不夠時調(diào)用fill函數(shù),fill函數(shù)內(nèi)部調(diào)用read函數(shù)讀取輸入流中的數(shù)據(jù)再填充buf緩沖區(qū)。)

為什么需要緩沖呢?原因很簡單,效率問題!緩沖中的數(shù)據(jù)實際上是保存在內(nèi)存中,而原始數(shù)據(jù)可能是保存在硬盤或NandFlash等存儲介質(zhì)中;而我們知道,從內(nèi)存中讀取數(shù)據(jù)的速度比從硬盤讀取數(shù)據(jù)的速度至少快10倍以上。
那干嘛不干脆一次性將全部數(shù)據(jù)都讀取到緩沖中呢?第一,讀取全部的數(shù)據(jù)所需要的時間可能會很長。第二,內(nèi)存價格很貴,容量不像硬盤那么大。

該類最關(guān)鍵的函數(shù)及fill()方法,其他方法都很好理解。該方法負責讀取輸入流的數(shù)據(jù)來填充buf緩沖區(qū)。具體解釋可參考http://www.cnblogs.com/skywang12345/p/io_12.html。

BufferedOutputStream

看過BufferedInputStream源碼之后BufferedOutputStream就看起來很簡單了,和BufferedInputStream一樣,BufferedOutputStream通過字節(jié)數(shù)組來緩沖數(shù)據(jù)(1024*8)。BufferedOutputStream當緩沖區(qū)滿或者用戶調(diào)用flush()函數(shù)時,它就會將緩沖區(qū)的數(shù)據(jù)寫入到輸出流中。

總結(jié)

  • BufferedInputStream是緩沖輸入流,作用是為另一個輸入流添加一些功能,比如緩沖輸入功能以及支持mark和reset方法的能力。
  • BufferedOutputStream是緩沖輸出流,通過設(shè)置這種輸出流,應(yīng)用程序就可以將單個或字節(jié)數(shù)組緩沖的寫入底層輸出流中,而不必針對每次字節(jié)寫入調(diào)用底層系統(tǒng)。

DataInputStream,DataOutputStream

DataInputStream

  • DataInputStream為數(shù)據(jù)輸入流,它允許應(yīng)用程序以與機器無關(guān)方式從底層輸入流中讀取基本Java數(shù)據(jù)類型。
  • DataOutputStream為數(shù)據(jù)輸出流,它允許應(yīng)用程序以適當方式將基本 Java數(shù)據(jù)類型寫入輸出流中。
public final String readUTF() throws IOException {
    return readUTF(this);
}

public final static String readUTF(DataInput in) throws IOException {
    int utflen = in.readUnsignedShort();
    byte[] bytearr = null;
    char[] chararr = null;
    if (in instanceof DataInputStream) {
        DataInputStream dis = (DataInputStream)in;
        if (dis.bytearr.length < utflen){
            dis.bytearr = new byte[utflen*2];
            dis.chararr = new char[utflen*2];
        }
        chararr = dis.chararr;
        bytearr = dis.bytearr;
    } else {
        bytearr = new byte[utflen];
        chararr = new char[utflen];
    }

    int c, char2, char3;
    int count = 0;
    int chararr_count=0;

    in.readFully(bytearr, 0, utflen);

    // 由于UTF-8的單字節(jié)和ASCII相同,所以這里就將它們進行預(yù)處理,直接保存到“字符數(shù)組chararr”中。
    // 對于其它的UTF-8數(shù)據(jù),則在后面進行處理。
    while (count < utflen) {
        c = (int) bytearr[count] & 0xff;
        if (c > 127) break;
        count++;
        chararr[chararr_count++]=(char)c;
    }

    while (count < utflen) {
        c = (int) bytearr[count] & 0xff;
        switch (c >> 4) {
            case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
                /* 0xxxxxxx*/
                count++;
                chararr[chararr_count++]=(char)c;
                break;
            case 12: case 13:
                /* 110x xxxx   10xx xxxx*/
                count += 2;
                if (count > utflen)
                    throw new UTFDataFormatException(
                        "malformed input: partial character at end");
                char2 = (int) bytearr[count-1];
                if ((char2 & 0xC0) != 0x80)
                    throw new UTFDataFormatException(
                        "malformed input around byte " + count);
                chararr[chararr_count++]=(char)(((c & 0x1F) << 6) |
                                                (char2 & 0x3F));
                break;
            case 14:
                /* 1110 xxxx  10xx xxxx  10xx xxxx */
                count += 3;
                if (count > utflen)
                    throw new UTFDataFormatException(
                        "malformed input: partial character at end");
                char2 = (int) bytearr[count-2];
                char3 = (int) bytearr[count-1];
                if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
                    throw new UTFDataFormatException(
                        "malformed input around byte " + (count-1));
                chararr[chararr_count++]=(char)(((c     & 0x0F) << 12) |
                                                ((char2 & 0x3F) << 6)  |
                                                ((char3 & 0x3F) << 0));
                break;
            default:
                /* 10xx xxxx,  1111 xxxx */
                throw new UTFDataFormatException(
                    "malformed input around byte " + count);
        }
    }
    // The number of chars produced may be less than utflen
    return new String(chararr, 0, chararr_count);
}

readUTF的執(zhí)行流程相當于把UTF-8編碼的輸入流中的字節(jié)數(shù)據(jù)先讀到了bytearr數(shù)組中,然后根據(jù)UTF-8編碼的特殊性,判斷數(shù)據(jù)是幾個字節(jié),根據(jù)情況又往chararr數(shù)組中轉(zhuǎn)。保證了每個字符轉(zhuǎn)化的正確率,最后所有的字符都正確的轉(zhuǎn)化到了chararr數(shù)組中,然后返回string值。

readUnsignedShort,readBoolean,readUnsignedByte

// UTF-8輸入流的前2個字節(jié)是數(shù)據(jù)的長度
public final int readUnsignedShort() throws IOException {
    int ch1 = in.read();
    int ch2 = in.read();
    if ((ch1 | ch2) < 0)
        throw new EOFException();
    return (ch1 << 8) + (ch2 << 0);
}

// 此方法適用于讀取用接口DataOutput的 writeBoolean方法寫入的字節(jié)
public final boolean readBoolean() throws IOException {
    //從輸入流中讀取一個字節(jié)
    int ch = in.read();
    //如果達到輸入流末尾,拋出異常
    if (ch < 0)
        throw new EOFException();
    //如果讀取的字節(jié)不是零,則返回true;如果是零,則返回false
    return (ch != 0);
}

// 讀取一個無符號為byte輸入字節(jié),將它數(shù)值位左側(cè)補零轉(zhuǎn)變?yōu)閕nt類型(覆蓋符號位),并返回結(jié)果,所以結(jié)果的范圍是0到255
public final int readUnsignedByte() throws IOException {
    int ch = in.read();
    if (ch < 0)
        throw new EOFException();
    return ch;
}

最后我們落實到該類的特點,它允許應(yīng)用程序以與機器無關(guān)方式從底層輸入流中讀取基本Java數(shù)據(jù)類型。

其實就是通過構(gòu)造時傳入的InputStream對象來根據(jù)要讀取的數(shù)據(jù)類型來讀取對應(yīng)的字節(jié)數(shù),比如readByte就調(diào)用一次in.read。然后把讀取出來的數(shù)據(jù)強轉(zhuǎn)成(byte)。readChar的話就調(diào)用兩次,然后因為兩個字節(jié)連續(xù)起來表示一個字符,那么就將先讀出來的字節(jié)適當?shù)囊莆徊蓚€字節(jié)+起來。這樣就相當于一次性讀出來兩個字節(jié),然后對該數(shù)據(jù)強轉(zhuǎn)即可得到最真實的數(shù)據(jù)。

需要注意的是,因為我們從輸入流中讀出的內(nèi)容返回的是int類型的,默認除了數(shù)據(jù)位,把我們原數(shù)據(jù)的符號位覆蓋掉了。然后我們強轉(zhuǎn)就可以就可以恢復(fù)(暫時先那么理解有符號位和無符號位的計算方式,方便記憶,雖然肯定不是這樣,以后再研究。)

DataOutputStream

incCount,flush,size

// 到目前為止寫入到輸出流中的字節(jié)數(shù) 最大值為Integer.MAX_VALUE
protected int written;

// 增加wirtten的值。最大值為Integer.MAX_VALUE
private void incCount(int value) {
    int temp = written + value;
    //int允許的最大值為Integer.MAX_VALUE,即2147483647,2147483647+1即為負數(shù)
    if (temp < 0) {
        temp = Integer.MAX_VALUE;
    }
    written = temp;
}

//  清空此數(shù)據(jù)輸出流。這迫使所有緩沖的輸出字節(jié)被寫出到流中。
public void flush() throws IOException {
    out.flush();
}

// 返回written的當前值,即到目前為止寫入此數(shù)據(jù)輸出流的字節(jié)數(shù)。最大值為Integer.MAX_VALUE。
public final int size() {
    return written;
}

writeShort,writeChar,writeFloat


public final void writeChar(int v) throws IOException {
    out.write((v >>> 8) & 0xFF);
    out.write((v >>> 0) & 0xFF);
    incCount(2);
}

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);
}

// 使用Float類中的floatToIntBits方法將float參數(shù)轉(zhuǎn)換為一個int值
public final void writeFloat(float v) throws IOException {
    writeInt(Float.floatToIntBits(v));
}

可以看到DataOutputStream和DataInputStream的處理方式是一樣的,要輸入到輸出流的數(shù)據(jù)有可能是1個字節(jié)或多個字節(jié)的,所以對應(yīng)不同的數(shù)據(jù)定義了不同的函數(shù),然后針對這些數(shù)值一個字節(jié)一個字節(jié)的進行輸入就好。

總結(jié)

  • DataInputStream提供了一系列從二進制流中讀取字節(jié),并根據(jù)所有Java基本類型數(shù)據(jù)進行重構(gòu)的readXXXX方法。同時還提供根據(jù)UTF-8編碼格式的數(shù)據(jù)寫入輸入流的方式,即readUTF方法。
  • DataOutputStream提供了一系列將數(shù)據(jù)從任意Java基本類型轉(zhuǎn)換為一系列字節(jié),并將這些字節(jié)寫入二進制流的writeXXXX方法。同時還提供了一個將String轉(zhuǎn)換成UTF-8修改版格式并寫入所得到的系列字節(jié)的工具,即writeUTF方法。

PrintStream

由于學習的時候,我在學習字節(jié)流的時候跳過了PrintStream,先看的PrintWriter所以看過PrintWriter后再來看PrintStream就感覺很簡單了,所以簡單記錄下。

  • PrintStream 是打印輸出流,它繼承于FilterOutputStream。
  • PrintStream 是用來裝飾其它輸出流。它能為其他輸出流添加了功能,使它們能夠方便地打印各種數(shù)據(jù)值表示形式。為底層輸出流提供了緩存池(BufferedWriter)。
  • 與其他輸出流不同,PrintStream 永遠不會拋出 IOException;它產(chǎn)生的IOException會被自身的函數(shù)所捕獲并設(shè)置錯誤標記, 用戶可以通過 checkError() 返回錯誤標記,從而查看PrintStream內(nèi)部是否產(chǎn)生了IOException。
  • 另外,PrintStream 提供了自動flush 和 字符集設(shè)置功能。所謂自動flush,就是往PrintStream寫入的數(shù)據(jù)會立刻調(diào)用flush()函數(shù)。
public class PrintStream extends FilterOutputStream
    implements Appendable, Closeable
{
    // 自動flush
    // 所謂“自動flush”,就是每次執(zhí)行print(), println(), write()函數(shù),都會調(diào)用flush()函數(shù);
    // 而“不自動flush”,則需要我們手動調(diào)用flush()接口。
    private final boolean autoFlush;
    // PrintStream是否右產(chǎn)生異常。當PrintStream有異常產(chǎn)生時,會被本身捕獲,并設(shè)置trouble為true
    private boolean trouble = false;
    // 用于格式化的對象
    private Formatter formatter;

    // BufferedWriter對象,用于實現(xiàn)“PrintStream支持字符集”。
    // 因為PrintStream是OutputStream的子類,所以它本身不支持字符串;
    // 但是BufferedWriter支持字符集,因此可以通過OutputStreamWriter創(chuàng)建PrintStream對應(yīng)的BufferedWriter對象,從而支持字符集。
    private BufferedWriter textOut;
    private OutputStreamWriter charOut;

    private static <T> T requireNonNull(T obj, String message) {
        if (obj == null)
            throw new NullPointerException(message);
        return obj;
    }
}

==需要注意的是== :很明顯,該類和PrintWriter還有個最大的區(qū)別。繼承自FilterOutputStream,也就是它做的是裝飾模式中的具體裝飾類。至于Appendable,Closeable接口和PrintWriter則沒區(qū)別,PrintWriter其父類Writer抽象也早實現(xiàn)了。

public class PrintStream extends FilterOutputStream
    implements Appendable, Closeable
{

PrintStream和DataOutputStream異同點

相同點:都是繼承與FileOutputStream,用于包裝其它輸出流。

不同點:

  1. PrintStream和DataOutputStream 都可以將數(shù)據(jù)格式化輸出;但它們在“輸出字符串”時的編碼不同。
    • PrintStream是輸出時采用的是用戶指定的編碼(創(chuàng)建PrintStream時指定的),若沒有指定,則采用系統(tǒng)默認的字符編碼。而DataOutputStream則采用的是UTF-8。
  2. 它們的寫入數(shù)據(jù)時的異常處理機制不同。
    • DataOutputStream在通過write()向“輸出流”中寫入數(shù)據(jù)時,若產(chǎn)生IOException,會拋出。
    • 而PrintStream在通過write()向“輸出流”中寫入數(shù)據(jù)時,若產(chǎn)生IOException,則會在write()中進行捕獲處理;并設(shè)置trouble標記(用于表示產(chǎn)生了異常)為true。用戶可以通過checkError()返回trouble值,從而檢查輸出流中是否產(chǎn)生了異常。
  3. 構(gòu)造函數(shù)不同
    • DataOutputStream的構(gòu)造函數(shù)只有一個:DataOutputStream(OutputStream out)。即它只支持以輸出流out作為“DataOutputStream的輸出流”。
    • 而PrintStream的構(gòu)造函數(shù)有許多:和DataOutputStream一樣,支持以輸出流out作為“PrintStream輸出流”的構(gòu)造函數(shù);還支持以“File對象”或者“String類型的文件名對象”的構(gòu)造函數(shù)。
    • 而且,在PrintStream的構(gòu)造函數(shù)中,能“指定字符集”和“是否支持自動flush()操作”。
  4. 目的不同
    • DataOutputStream的作用是裝飾其它的輸出流,它和DataInputStream配合使用:允許應(yīng)用程序以與機器無關(guān)的方式從底層輸入流中讀寫java數(shù)據(jù)類型。
    • 而PrintStream的作用雖然也是裝飾其他輸出流,但是它的目的不是以與機器無關(guān)的方式從底層讀寫java數(shù)據(jù)類型;而是為其它輸出流提供打印各種數(shù)據(jù)值表示形式,使其它輸出流能方便的通過print(),println()或printf()等輸出各種格式的數(shù)據(jù)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容