最近幫學姐寫爬蟲的時候遇到奇怪的問題,同樣的程序在Mac上可以正常運行而在Windows上返回結果錯誤,最后經(jīng)排查發(fā)現(xiàn)是Linux與Windows的默認編碼方式不同,而自己的程序沒有設置編碼方式自動采用了默認的編碼方式,所以導致錯誤發(fā)生。之后嘗試了多種編碼方式均告失敗,最后發(fā)現(xiàn)是由于自己對輸入輸出流的認識不到位,沒有正確使用的原因,故進行整理學習。
首先認識一下字節(jié)流與字符流。程序中的輸入輸出都是通過流的形式保存的,流中保存的全是字節(jié)文件。根據(jù)處理數(shù)據(jù)類型不同可以分為字節(jié)流和字符流。字節(jié)流是字符流的基礎。
字節(jié)流:字節(jié)流處理單元為一個字節(jié),操作字節(jié)和字節(jié)數(shù)組。如果是音頻、圖片等建議用字節(jié)流。
字符流:字符流處理單元為兩個字節(jié)的UNICODE字符,操作字符家、字符數(shù)組和字符串,對多國語言支持性較好,如果是文本建議用字符流。
基于字節(jié)流的Stream:通常以OutputStream和InputStream結尾,DataOutputStream、DataInputStream、FileOutputStream……
**基于字符流的Stream:通常以Writer和Reader結尾,PrintWriter、FileWriter、FileReader、StringWriter……
可以發(fā)現(xiàn)絕大部份流都是成對出現(xiàn)的,包括輸入流和輸出流??梢赃@樣理解輸入輸出流
輸入流(InputStream和Reader)可以看作一個出水的水龍頭,具有流出水流的功能,即向程序產(chǎn)生數(shù)據(jù)的功能,read便相當于打開開關,之后便會流出水流(數(shù)據(jù))。
輸出流(OutputStream和Writer)可以看作一個進水的水龍頭,具有儲存水流的功能,即接收程序產(chǎn)生的數(shù)據(jù),write后也相當于打開開關,水流(數(shù)據(jù))流進進水水龍頭。
介紹完了基本概念,現(xiàn)在來看一下基本用法。
| InputStream | |
|---|---|
| 從流中讀取數(shù)據(jù) | |
| public abstract int read() throws IOException | 從輸入流中讀取下一字節(jié)。返回0到255范圍內的int字節(jié)值。如果因為已經(jīng)到達流末尾而沒有可用的字節(jié),則返回-1。 |
| public int read(byte[] b) throws IOException | 從輸入流中讀取一定數(shù)量的字節(jié),并將其存儲在緩沖區(qū)數(shù)組b中。以整數(shù)形式返回實際讀取的字節(jié)數(shù)。等同于read(byte[],int,int) |
| public int read(byte[] b, int off, int len) throws IOException | 將輸入流中最多l(xiāng)en個數(shù)據(jù)字節(jié)讀入byte數(shù)組。嘗試讀取len個字節(jié),但讀取的字節(jié)也可能小于該值。將讀取的第一個字節(jié)存儲在元素b[off]到b[off+k-1]的元素中,以此類推。 |
| public long skip(long n) throws IOException | 跳過和丟棄此輸入流中數(shù)據(jù)的 n 個字節(jié)。出于各種原因,skip 方法結束時跳過的字節(jié)數(shù)可能小于該數(shù),也可能為 0。導致這種情況的原因很多,跳過 n 個字節(jié)之前已到達文件末尾只是其中一種可能。返回跳過的實際字節(jié)數(shù)。如果 n 為負,則不跳過任何字節(jié)。此類的 skip 方法創(chuàng)建一個 byte 數(shù)組,然后重復將字節(jié)讀入其中,直到讀夠 n 個字節(jié)或已到達流末尾為止。建議子類提供此方法更為有效的實現(xiàn)。例如,可依賴搜索能力的實現(xiàn)。 |
| public int available() throws IOException | 返回此輸入流下一個方法調用可以不受阻塞地從此輸入流讀?。ɑ蛱^)的估計字節(jié)數(shù)(流中尚未被讀取的字節(jié)數(shù))。下一個調用可能是同一個線程,也可能是另一個線程。一次讀取或跳過此估計數(shù)個字節(jié)不會受阻塞,但讀取或跳過的字節(jié)數(shù)可能小于該數(shù)。注意,有些 InputStream 的實現(xiàn)將返回流中的字節(jié)總數(shù),但也有很多實現(xiàn)不會這樣做。試圖使用此方法的返回值分配緩沖區(qū),以保存此流所有數(shù)據(jù)的做法是不正確的。如果已經(jīng)調用 close() 方法關閉了此輸入流,那么此方法的子類實現(xiàn)可以選擇拋出 IOException。類 InputStream 的 available 方法總是返回 0。此方法應該由子類重寫。 |
| 關閉流 | |
| public void close() throws IOException | 關閉輸入流并釋放與該流關聯(lián)的所有系統(tǒng)資源 |
| 使用輸入流中的標記 | |
| public void mark(int readlimit) | 在此輸入流中標記當前位置。對 reset 方法的后續(xù)調用會在最后標記的位置重新定位此流,以便后續(xù)讀取重新讀取相同的字節(jié)。readlimit 參數(shù)表示讀取readmit個字節(jié)數(shù)后標記失效。 |
| public void reset() throws IOException | 將讀指針重新指向mark方法記錄的位置。 |
| public boolean markSupported() | 測試此輸入流是否支持mark()和reset()方法。 |
| OutputStream | |
|---|---|
| 輸出數(shù)據(jù) | |
| public abstract void write(int b) throws IOException | 將指定的字節(jié)寫入輸出流。write 的常規(guī)協(xié)定是:向輸出流寫入一個字節(jié)。要寫入的字節(jié)是參數(shù) b 的八個低位。b 的 24 個高位將被忽略。 |
| public void write(byte[] b) throws IOException | 將b.length個字節(jié)從指定的byte數(shù)組寫入此輸出流。與write(b, 0, b.length)等同 |
| public void write(byte[] b, int off, int len) throws IOException | 將指定數(shù)組中從偏移量off開始的len個字節(jié)寫入此輸出流。 |
| 刷新流 | |
| public void flush() throws IOException | 刷新此輸出流并強制寫出所有緩沖的字節(jié)。如果此流的預期目標是由基礎操作系統(tǒng)提供的一個抽象(如一個文件),則刷新此流只能保證將以前寫入到流的字節(jié)傳遞給操作系統(tǒng)進行寫入,但不保證能將這些字節(jié)實際寫入到物理設備(如磁盤驅動器)。 |
| 關閉流 | |
| public void close() throws IOException | 關閉此輸出流并釋放與此流有關的所有系統(tǒng)資源 |
通過輸入輸出流復制圖片的例子:
public class Test {
public static void main(String[] args) throws IOException{
long startTime = System.currentTimeMillis();
InputStream is = new FileInputStream(new File("/Users/zhaokang/Desktop/1.jpg"));
OutputStream os = new FileOutputStream(new File("/Users/zhaokang/Desktop/2.jpg"));
int i = 0;
while(i != -1){
i = is.read();
os.write(i);
}
is.close();
os.close();
long endTime = System.currentTimeMillis();
System.out.println("程序運行時間:"+(endTime-startTime)+"ms");
}
}
//輸出結果為:程序運行時間40231ms
通過緩沖流提高復制速度
public class Test {
public static void main(String[] args) throws IOException{
long startTime = System.currentTimeMillis();
BufferedInputStream bis = new BufferedInputStream(new
FileInputStream(new File("/Users/zhaokang/Desktop/1.jpg")));
BufferedOutputStream bos = new BufferedOutputStream(new
FileOutputStream(new File("/Users/zhaokang/Desktop/2.jpg")));
int i = 0;
while(i != -1){
i = bis.read();
bos.write(i);
}
bos.flush();
bis.close();
bos.close();
long endTime = System.currentTimeMillis();
System.out.println("程序運行時間:"+(endTime-startTime)+"ms");
}
}
//輸出結果為:程序運行時間486ms
文件較大時,做一個緩沖處理
public class Test {
public static void main(String[] args) throws IOException{
long startTime = System.currentTimeMillis();
byte[] tmp = new byte[1024];
InputStream is = new FileInputStream(new File("/Users/zhaokang/Desktop/1.jpg"));
OutputStream os = new FileOutputStream(new File("/Users/zhaokang/Desktop/2.jpg"));
int i = 0;
while(i != -1){
i = is.read(tmp);
os.write(tmp);
}
is.close();
os.close();
long endTime = System.currentTimeMillis();
System.out.println("程序運行時間:"+(endTime-startTime)+"ms");
}
}
//輸出結果為:程序運行時間61ms
雙緩沖
public class Test {
public static void main(String[] args) throws IOException{
long startTime = System.currentTimeMillis();
byte[] tmp = new byte[1024];
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("/Users/zhaokang/Desktop/1.jpg")));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("/Users/zhaokang/Desktop/2.jpg")));
int i = 0;
while(i != -1){
i = bis.read(tmp);
bos.write(tmp);
}
bos.flush();
bis.close();
bos.close();
long endTime = System.currentTimeMillis();
System.out.println("程序運行時間:"+(endTime-startTime)+"ms");
}
}
//輸出結果為:程序運行時間29ms
可以看到第一種情況效率最低,所以若非特殊要求可以放棄這種方法。