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