- 說明
- IntputStream,OutputStream 簡介
- 助于理解
- ByteArrayInputStream
- ByteArrayOutputStream
- PipedInputStream,PipedOutputStream
- PipedOutputStream
- FilterInputStream,F(xiàn)ilterOutputStream
- BufferedInputStream,BufferedOutputStream
- DataInputStream,DataOutputStream
- PrintStream
說明
整個系列的文章全部參考或直接照搬下面兩位作者的文章,這里只是根據(jù)自己需要對原作者的文章梳理的總結(jié),僅給自己日后復(fù)習時提供思路,如有讀者看到學習時建議移步原作。再次重申并非我所寫
- 潘威威:Java8 I/O源碼-目錄
- skywang12345:Java I/O系列
另一篇本人總結(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裝飾類,可以進一步重寫這些方法中的一些方法,來提供裝飾功能。它的常用子類有BufferedInputStream和DataInputStream。比如,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,用于包裝其它輸出流。
不同點:
- PrintStream和DataOutputStream 都可以將數(shù)據(jù)格式化輸出;但它們在“輸出字符串”時的編碼不同。
- PrintStream是輸出時采用的是用戶指定的編碼(創(chuàng)建PrintStream時指定的),若沒有指定,則采用系統(tǒng)默認的字符編碼。而DataOutputStream則采用的是UTF-8。
- 它們的寫入數(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)生了異常。
- 構(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()操作”。
- 目的不同
- DataOutputStream的作用是裝飾其它的輸出流,它和DataInputStream配合使用:允許應(yīng)用程序以與機器無關(guān)的方式從底層輸入流中讀寫java數(shù)據(jù)類型。
- 而PrintStream的作用雖然也是裝飾其他輸出流,但是它的目的不是以與機器無關(guān)的方式從底層讀寫java數(shù)據(jù)類型;而是為其它輸出流提供打印各種數(shù)據(jù)值表示形式,使其它輸出流能方便的通過print(),println()或printf()等輸出各種格式的數(shù)據(jù)。