最近幫學(xué)姐寫爬蟲的時候遇到奇怪的問題,同樣的程序在Mac上可以正常運(yùn)行而在Windows上返回結(jié)果錯誤凑保,最后經(jīng)排查發(fā)現(xiàn)是Linux與Windows的默認(rèn)編碼方式不同,而自己的程序沒有設(shè)置編碼方式自動采用了默認(rèn)的編碼方式须尚,所以導(dǎo)致錯誤發(fā)生动分。之后嘗試了多種編碼方式均告失敗,最后發(fā)現(xiàn)是由于自己對輸入輸出流的認(rèn)識不到位捷犹,沒有正確使用的原因弛饭,故進(jìn)行整理學(xué)習(xí)。
首先認(rèn)識一下字節(jié)流與字符流萍歉。程序中的輸入輸出都是通過流的形式保存的侣颂,流中保存的全是字節(jié)文件。根據(jù)處理數(shù)據(jù)類型不同可以分為字節(jié)流和字符流枪孩。字節(jié)流是字符流的基礎(chǔ)憔晒。
字節(jié)流:字節(jié)流處理單元為一個字節(jié),操作字節(jié)和字節(jié)數(shù)組蔑舞。如果是音頻拒担、圖片等建議用字節(jié)流。
字符流:字符流處理單元為兩個字節(jié)的UNICODE字符斗幼,操作字符家澎蛛、字符數(shù)組和字符串,對多國語言支持性較好蜕窿,如果是文本建議用字符流谋逻。
基于字節(jié)流的Stream:通常以O(shè)utputStream和InputStream結(jié)尾呆馁,DataOutputStream、DataInputStream毁兆、FileOutputStream……
**基于字符流的Stream:通常以Writer和Reader結(jié)尾浙滤,PrintWriter、FileWriter气堕、FileReader纺腊、StringWriter……
可以發(fā)現(xiàn)絕大部份流都是成對出現(xiàn)的,包括輸入流和輸出流茎芭∫灸ぃ可以這樣理解輸入輸出流
輸入流(InputStream和Reader)可以看作一個出水的水龍頭,具有流出水流的功能梅桩,即向程序產(chǎn)生數(shù)據(jù)的功能壹粟,read便相當(dāng)于打開開關(guān),之后便會流出水流(數(shù)據(jù))宿百。
輸出流(OutputStream和Writer)可以看作一個進(jìn)水的水龍頭趁仙,具有儲存水流的功能,即接收程序產(chǎn)生的數(shù)據(jù)垦页,write后也相當(dāng)于打開開關(guān)雀费,水流(數(shù)據(jù))流進(jìn)進(jìn)水水龍頭。
介紹完了基本概念痊焊,現(xiàn)在來看一下基本用法盏袄。
InputStream | |
---|---|
從流中讀取數(shù)據(jù) | |
public abstract int read() throws IOException | 從輸入流中讀取下一字節(jié)。返回0到255范圍內(nèi)的int字節(jié)值宋光。如果因為已經(jīng)到達(dá)流末尾而沒有可用的字節(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é)束時跳過的字節(jié)數(shù)可能小于該數(shù)旷坦,也可能為 0掏熬。導(dǎo)致這種情況的原因很多,跳過 n 個字節(jié)之前已到達(dá)文件末尾只是其中一種可能秒梅。返回跳過的實際字節(jié)數(shù)旗芬。如果 n 為負(fù),則不跳過任何字節(jié)捆蜀。此類的 skip 方法創(chuàng)建一個 byte 數(shù)組疮丛,然后重復(fù)將字節(jié)讀入其中,直到讀夠 n 個字節(jié)或已到達(dá)流末尾為止辆它。建議子類提供此方法更為有效的實現(xiàn)誊薄。例如,可依賴搜索能力的實現(xiàn)锰茉。 |
public int available() throws IOException | 返回此輸入流下一個方法調(diào)用可以不受阻塞地從此輸入流讀认疚荨(或跳過)的估計字節(jié)數(shù)(流中尚未被讀取的字節(jié)數(shù))。下一個調(diào)用可能是同一個線程洞辣,也可能是另一個線程咐刨。一次讀取或跳過此估計數(shù)個字節(jié)不會受阻塞,但讀取或跳過的字節(jié)數(shù)可能小于該數(shù)扬霜。注意定鸟,有些 InputStream 的實現(xiàn)將返回流中的字節(jié)總數(shù),但也有很多實現(xiàn)不會這樣做著瓶。試圖使用此方法的返回值分配緩沖區(qū)联予,以保存此流所有數(shù)據(jù)的做法是不正確的。如果已經(jīng)調(diào)用 close() 方法關(guān)閉了此輸入流材原,那么此方法的子類實現(xiàn)可以選擇拋出 IOException沸久。類 InputStream 的 available 方法總是返回 0。此方法應(yīng)該由子類重寫余蟹。 |
關(guān)閉流 | |
public void close() throws IOException | 關(guān)閉輸入流并釋放與該流關(guān)聯(lián)的所有系統(tǒng)資源 |
使用輸入流中的標(biāo)記 | |
public void mark(int readlimit) | 在此輸入流中標(biāo)記當(dāng)前位置卷胯。對 reset 方法的后續(xù)調(diào)用會在最后標(biāo)記的位置重新定位此流,以便后續(xù)讀取重新讀取相同的字節(jié)威酒。readlimit 參數(shù)表示讀取readmit個字節(jié)數(shù)后標(biāo)記失效窑睁。 |
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 | 刷新此輸出流并強(qiáng)制寫出所有緩沖的字節(jié)苏遥。如果此流的預(yù)期目標(biāo)是由基礎(chǔ)操作系統(tǒng)提供的一個抽象(如一個文件)送挑,則刷新此流只能保證將以前寫入到流的字節(jié)傳遞給操作系統(tǒng)進(jìn)行寫入,但不保證能將這些字節(jié)實際寫入到物理設(shè)備(如磁盤驅(qū)動器)暖眼。 |
關(guān)閉流 | |
public void close() throws IOException | 關(guān)閉此輸出流并釋放與此流有關(guān)的所有系統(tǒng)資源 |
通過輸入輸出流復(fù)制圖片的例子:
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("程序運(yùn)行時間:"+(endTime-startTime)+"ms");
}
}
//輸出結(jié)果為:程序運(yùn)行時間40231ms
通過緩沖流提高復(fù)制速度
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("程序運(yùn)行時間:"+(endTime-startTime)+"ms");
}
}
//輸出結(jié)果為:程序運(yùn)行時間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("程序運(yùn)行時間:"+(endTime-startTime)+"ms");
}
}
//輸出結(jié)果為:程序運(yùn)行時間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("程序運(yùn)行時間:"+(endTime-startTime)+"ms");
}
}
//輸出結(jié)果為:程序運(yùn)行時間29ms
可以看到第一種情況效率最低,所以若非特殊要求可以放棄這種方法诫肠。