目錄:系統(tǒng)學(xué)習(xí) Java IO ---- 目錄漓库,概覽
InputStream
是Java IO API中所有輸入流的父類海雪。
表示有序的字節(jié)流菊匿,換句話說城丧,可以將 InputStream 中的數(shù)據(jù)作為有序的字節(jié)序列讀取影锈。
這在從文件讀取數(shù)據(jù)或通過網(wǎng)絡(luò)接收時非常有用芹务。
InputStream 通常連接到某些數(shù)據(jù)源,如文件鸭廷,網(wǎng)絡(luò)連接枣抱,管道等
看如下代碼片段:
public class InputStreamExample {
public static void main(String[] args) throws IOException {
InputStream inputStream = new FileInputStream("D:\\out.txt");
//do something with data...
int data = inputStream.read();
while (data != -1) {
System.out.print((char) data);
data = inputStream.read();
}
inputStream.close();
}
}
注意:為了代碼清晰,這里并沒有考慮處理異常的情況辆床,IO 異常處理有專門的介紹佳晶。
read()
此方法返回的是 int 值,其中包含讀取的字節(jié)的字節(jié)值,可以將返回的 int 強(qiáng)制轉(zhuǎn)換為 char 輸出讼载。
如果 read() 方法返回 -1 轿秧,則表示已到達(dá)流的末尾中跌,這意味著在 InputStream 中不再有要讀取的數(shù)據(jù)。
也就是說淤刃,-1 作為 int 值晒他,而不是 -1 作為 char 或 short,這里有區(qū)別逸贾!
InputStream 類還包含兩個 read() 方法陨仅,這些方法可以將 InputStream 源中的數(shù)據(jù)讀入字節(jié)數(shù)組。
這些方法是:
- int read(byte[]);
- int read(byte[], int offset, int length);
一次讀取一個字節(jié)數(shù)比一次讀取一個字節(jié)要快得多铝侵,所以在可以的時候灼伤,使用這些讀取方法而不是 read() 方法。
read(byte [])方法將嘗試將盡可能多的字節(jié)讀入作為參數(shù)給出的字節(jié)數(shù)組咪鲜,因?yàn)閿?shù)組具有空間狐赡。
該方法返回一個 int ,其值是實(shí)際讀取了多少字節(jié)疟丙,這點(diǎn)和 read() 方法不一樣颖侄。
如果可以從 InputStream 讀取的字節(jié)少于字節(jié)數(shù)組的空間,則字節(jié)數(shù)組的其余部分將包含與讀取開始之前相同的數(shù)據(jù)享郊。例如:
InputStream input = new ByteArrayInputStream("123456789".getBytes());
byte[] bytes = new byte[4]; // 每次只讀取 4 個字節(jié)
int data = input.read(bytes);
while (data != -1) {
System.out.print(new String(bytes));
data = input.read(bytes);
}
將輸出 123456789678 览祖,而不是預(yù)期的 123456789 !
因?yàn)榈谝淮巫x取進(jìn) bytes 是 1234 炊琉,第二次將是 5678 展蒂,現(xiàn)在只剩下 9 一個數(shù)字了,注意此時 bytes 的值是 5678 苔咪,然后再讀取剩下 1個 9锰悼,不能裝滿 bytes 了,只能覆蓋 bytes的第一個字節(jié)团赏,最后返回的bytes 是 9678箕般。
所以記住檢查返回的 int 以查看實(shí)際讀入字節(jié)數(shù)組的字節(jié)數(shù)。
int read(byte[], int offset, int length);方法和 read(byte [])方法差不多舔清,只是增加了偏移量和指定長度隘世。
和 read() 一樣,都是返回 -1 表示數(shù)據(jù)讀取結(jié)束鸠踪。
使用實(shí)例如下:
InputStream inputstream = new FileInputStream("D://out.txt");
byte[] data = new byte[1024];
int bytesRead = inputstream.read(data);
while(bytesRead != -1) {
doSomethingWithData(data, bytesRead);
bytesRead = inputstream.read(data);
}
inputstream.close();
首先丙者,此示例創(chuàng)建一個字節(jié)數(shù)組。
然后它創(chuàng)建一個名為 bytesRead 的 int 變量來保存每次讀取 byte [] 調(diào)用時讀取的字節(jié)數(shù)营密,
并立即分配 bytesRead 從第一次讀取 byte [] 調(diào)用返回的值械媒。
mark() and reset()
InputStream 類有兩個名為 mark() 和 reset() 的方法,InputStream 的子類可能支持也可能不支持:
- 該子類覆蓋 markSupported() 并返回true,則支持 mark( )和 reset() 方法纷捞。
- 該子類覆蓋 markSupported() 并返回 false 痢虹,則不支持 mark() 和 reset() 。
- 該子類不重寫 markSupported() 方法 主儡,則是父類的默認(rèn)實(shí)現(xiàn)
public boolean markSupported() { return false; }
也是不支持 mark( )和 reset() 方法
mark() 在 InputStream 內(nèi)部設(shè)置一個標(biāo)記奖唯,默認(rèn)值在位置 0 處。
可以手動標(biāo)記到目前為止已讀取數(shù)據(jù)的流中的點(diǎn)糜值,然后丰捷,代碼可以繼續(xù)從 InputStream 中讀取數(shù)據(jù)。
如果想要返回到設(shè)置標(biāo)記的流中的點(diǎn)寂汇,在 InputStream 上調(diào)用 reset() 病往,然后 InputStream “倒退”并返回標(biāo)記,
如此骄瓣,便可再次從該mark點(diǎn)開始返回(讀韧O铩)數(shù)據(jù)。很明顯這可能會導(dǎo)致一些數(shù)據(jù)從 InputStream 返回多次榕栏。我來舉個例子:
public static void testMarkAndReset() throws IOException {
InputStream input = new ByteArrayInputStream("123456789".getBytes());
System.out.println("第一次打优锨凇:");
int count = 0;// 計(jì)算是第幾次讀取,將在第二次讀取時做標(biāo)記扒磁;
byte[] bytes = new byte[3]; // 每次只讀取 3 個字節(jié)
int data = input.read(bytes);
while (data != -1) {
System.out.print(new String(bytes));
if (++count == 2) { // 在第二輪讀取庆揪,即讀到數(shù)字 4 的時候,做標(biāo)記
input.mark(16); // 從 mark 點(diǎn)開始再過 readlimit 個字節(jié)渗磅,mark 將失效
}
data = input.read(bytes);
}
input.reset();
System.out.println("\n在經(jīng)過 mark 和 reset 之后從 mark 位置開始打印:");
data = input.read(bytes);
while (data != -1) {
System.out.print(new String(bytes));
data = input.read(bytes);
}
}
將會輸出:
第一次打蛹旆谩:
123456789
在經(jīng)過 mark 和 reset 之后從 mark 位置開始打邮加恪:
789
另外要說明一下 mark(int readlimit) 參數(shù),readlimit 是告訴系統(tǒng)脆贵,過了這個 mark 點(diǎn)之后医清,給本宮記住往后的 readlimit 個字節(jié),因?yàn)榈綍r候 reset 之后卖氨,要從 mark 點(diǎn)開始讀取的会烙;但實(shí)際情況和 jdk 文檔有出入,很多情況下調(diào)用 mark(int readlimit) 方法后,即使讀取超過 readlimit 字節(jié)的數(shù)據(jù)筒捺,mark 標(biāo)記仍有效,這又是為什么呢柏腻?網(wǎng)上有人解答,但我還是決定親自探索一番系吭。
我們這個實(shí)例引用的實(shí)際對象是 ByteArrayInputStream
先看一下它的源碼:
/* Note: The readAheadLimit for this class has no meaning.*/
public void mark(int readAheadLimit) {
mark = pos;
}
好家伙五嫂,它說這個參數(shù)對于這個類沒有任何作用。
注意:這段是源碼分析可看可不看,跳過不影響閱讀
那我們在看看其他的 InputStream 子類沃缘,經(jīng)驗(yàn)證躯枢,F(xiàn)ileInputStream 和一些實(shí)現(xiàn)類不支持 mark() 方法,我們看看 BufferedInputStream
類源碼:
我先把一些字段的含義說明一下:
-
count
索引1大于緩沖區(qū)中最后一個有效字節(jié)的索引槐臀。 該值始終在0到buf.length的范圍內(nèi); 元素buf [0]到buf [count-1]包含從底層輸入流獲得的緩沖輸入數(shù)據(jù)锄蹂。在 read() 方法中讀完數(shù)據(jù)返回 -1 就是因?yàn)?code>if (pos >= count) return -1; -
pos
指緩沖區(qū)中的當(dāng)前位置。 這是要從 buf 數(shù)組中讀取的下一個字符的索引水慨。
該值始終在 0 到 count 范圍內(nèi)得糜。 如果它小于 count,則 buf [pos] 是要作為輸入提供的下一個字節(jié); 如果它等于 count 讥巡,則下一個讀取或跳過操作將需要從包含的輸入流中讀取更多字節(jié)掀亩。(即重新從輸入流中取出一段數(shù)據(jù)緩存) -
markpos
是調(diào)用最后一個 mark() 方法時 pos 字段的值。該值始終在-1到pos的范圍內(nèi)欢顷。 如果輸入流中沒有標(biāo)記位置槽棍,則此字段為-1。
BufferedInputStream 是每次讀取一定量的數(shù)據(jù)到 buf 數(shù)組中的抬驴,設(shè)置了 readlimit 肯定是想讓數(shù)組從 mark 索引開始至少記錄到 (mark + readlimit) 索引炼七。
public synchronized void mark(int readlimit) {
marklimit = readlimit;
markpos = pos;
}
public synchronized void reset() throws IOException {
getBufIfOpen(); // Cause exception if closed
if (markpos < 0)
throw new IOException("Resetting to invalid mark");
pos = markpos;
}
private void fill() throws IOException {
byte[] buffer = getBufIfOpen();
if (markpos < 0)
pos = 0; /* 如果標(biāo)記點(diǎn)不在緩沖數(shù)組里(沒標(biāo)記點(diǎn)),丟掉buffer布持,取新數(shù)據(jù) */
else if (pos >= buffer.length) /* 緩沖區(qū)中當(dāng)前位置比buffer數(shù)組大豌拙,才執(zhí)行下面代碼 */
if (markpos > 0) { /* 可以把 markpos 左邊的數(shù)據(jù)丟掉 */
int sz = pos - markpos; // 需要緩存的字節(jié)長度,從 markpos 開始
System.arraycopy(buffer, markpos, buffer, 0, sz); // 復(fù)用內(nèi)存空間
pos = sz;
markpos = 0;
} else if (buffer.length >= marklimit) { // 如果 buffer 的長度已經(jīng)大于 marklimit
markpos = -1; /* 那 mark 就失效了*/
pos = 0; /* 刪除buffer內(nèi)容题暖,取新數(shù)據(jù) */
} else if (buffer.length >= MAX_BUFFER_SIZE) { // 如果buffer過長就拋錯
throw new OutOfMemoryError("Required array size too large");
} else { /* buffer 還沒 marklimit 大按傅,擴(kuò)容到 pos 的2倍或者最大值 */
int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
pos * 2 : MAX_BUFFER_SIZE;
if (nsz > marklimit)
nsz = marklimit;
byte nbuf[] = new byte[nsz];
System.arraycopy(buffer, 0, nbuf, 0, pos);
if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
throw new IOException("Stream closed");
}
buffer = nbuf;
}
count = pos;
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0)
count = n + pos;
}
可以得出:設(shè)置標(biāo)記后,
- 如果緩沖區(qū)中當(dāng)前位置比 buffer 數(shù)組小胧卤,也就是還沒讀完 buffer 數(shù)組交汤,那 mark 標(biāo)記不會失效拓瞪;
- 下次繼續(xù)讀取檐春,超過 buffer 大小字節(jié)后唠椭,判斷 markpos 是否大于0,如果 markpos 大于0叶撒,即還在 buffer 數(shù)組內(nèi)绝骚,則把 markpos 左邊的數(shù)據(jù)清除,markpos 指向 0 , 復(fù)用內(nèi)存空間祠够,并設(shè)置 buffer 的大小為 (pos - markpos) 的值压汪;
- 再繼續(xù)讀取,此時 markpos 肯定不在 buffer 數(shù)組包含范圍了古瓤,此時判斷 buffer 的長度是否大于等于
marklimit 蛾魄,如果小于 marklimit ,那說明設(shè)置 mark 后讀取的數(shù)據(jù)長度還沒達(dá)到要求的 marklimit 了,給我繼續(xù)滴须,保持從 mark 點(diǎn)開始緩存, mark 標(biāo)記不會失效舌狗。然后 buffer 就擴(kuò)容到Math.min(2倍 pos 或最大值 ,marklimit)扔水;
- 再繼續(xù)讀取痛侍,同上,buffer 這么努力擴(kuò)容魔市,總有大于 marklimit 的時候主届,這時說明設(shè)置 mark 后繼續(xù)讀取的數(shù)據(jù)長度已經(jīng)超過要求的 marklimit 了,仁盡義至待德,標(biāo)記失效君丁;
我們就只分析了 ByteArrayInputStream 和 BufferedInputSteam 類的算法,其它輸入流不知道将宪。因此 mark() 方法標(biāo)記時绘闷,務(wù)必考慮好 readlimit 的值。
OutputStream
OutputStream 通常始終連接到某個數(shù)據(jù)目標(biāo)较坛,如文件印蔗,網(wǎng)絡(luò)連接,管道等丑勤。 OutputStream 的目標(biāo)是將數(shù)據(jù)寫入到外部华嘹。
write(byte)
write(byte) 方法用于將單個字節(jié)寫入 OutputStream。 OutputStream 的 write() 方法接受一個 int 法竞,其中包含要寫入的字節(jié)的字節(jié)值耙厚。 只寫入 int 值的第一個字節(jié)。 其余的被忽略了岔霸。
OutputStream 的子類有重寫的 write() 方法薛躬。 例如,DataOutputStream 允許使用相應(yīng)的方法writeBoolean()秉剑,writeDouble() 等編寫諸如 int泛豪,long稠诲,float侦鹏,double,boolean 等 Java 基本類型臀叙。
write(byte[] bytes) 略水, write(byte[] bytes, int offset, int length)
和 InputStream 一樣,它們也可以將一個數(shù)組或一部分字節(jié)寫入 OutputStream 劝萤。
flush()
OutputStream 的flush() 方法將寫入 OutputStream 的所有數(shù)據(jù)刷新到底層數(shù)據(jù)目標(biāo)渊涝。 例如,如果 OutputStream 是 FileOutputStream ,則寫入 FileOutputStream 的字節(jié)可能尚未完全寫入磁盤跨释。 即使您的代碼已將其寫入 FileOutputStream 胸私,但數(shù)據(jù)也可能還在某處緩存在內(nèi)存中。 通過調(diào)用 flush() 可以確保將任何緩沖的數(shù)據(jù)刷新(寫入)到磁盤(或網(wǎng)絡(luò)鳖谈,或 OutputStream 的目標(biāo))岁疼。