Java中IO流分成兩大類,一種是輸入流,所有的輸入流都直接或間接繼承自InputStream抽象類,輸入流作為數據的來源,我們可以通過輸入流的read方法讀取字節數據;另一種是輸出流,所有的輸出流都直接或間接繼承自OutputStream抽象類,輸出流接收數據,可以通過write方法寫入字節數據。在Java的IO流類中,大部分的輸入流和輸出流都是成對存在的,即如果存在XXXInputStream,那么就存在XXXOutputStream,反之亦然。(SequenceInputStream和StringBufferInputStream是特例,沒有對應的SequenceOutputStream類和StringBufferOutputStream類,稍后會解釋)。許多IO操作都可能會拋出IOException異常,比如read、write、close操作。
以下是Java的IO流中常見的輸入流,由于每個輸入流都有其對應的輸出流,所以此處就不再列出輸出流的繼承結構圖。
下面依次對這些類進行介紹以及如何使用。vcD4NCjxociAvPg0KPGJsb2NrcXVvdGU+DQoJPHA+Qnl0ZUFycmF5SW5wdXRTdHJlYW0gJmFtcDsgQnl0ZUFycmF5T3V0cHV0U3RyZWFtPC9wPg0KPC9ibG9ja3F1b3RlPg0KPHA+Qnl0ZUFycmF5SW5wdXRTdHJlYW25udTsuq/K/dbQ0OjSqrSryOvSu7j2Ynl0Zcr91+nX986qyv2+3dS0o6y1sda00NByZWFkstnX98qxo6y+zbvhtNO4w8r91+nW0LbByKHK/b7do6zV/cjnxuTD+6OsysfSu9bWu/nT2tfWvdrK/dfpyrXP1rXE0rvW1rzytaXK5MjrwfejrM/UtvjS17z7tcTKx6OsyOe5+9TaubnU7Lqvyv3W0LSryOvBy251bGzX986q19a92sr9vt2jrMTHw7TU2ta00NByZWFkstnX98qxvs274bP2z9ZOdWxsUG9pbnRlckV4Y2VwdGlvbtLss6OjrLWrysfU2rm51Oy6r8r9s/XKvLuvvde2zrK7u+HF17P20uyzo6O70+vWrs/gttTTprXEysdCeXRlQXJyYXlPdXRwdXRTdHJlYW2jrMbkxNqyv9Ky09DSu7j219a92sr91+nTw9PatOa0ondyaXRlstnX98qx0LTI67XEyv2+3aOs1Nq5udTsuq/K/dbQv8nS1LSryOvSu7j2c2l6Zda4tqjG5MTasr+1xGJ5dGXK/dfptcS089Cho6zI57n7srvWuLaoo6zEx8O0xKzIz8v8u+G9q2J5dGXK/dfps/XKvLuvzqozMtfWvdqjrLWxs9bQ+M2ouf13cml0Zc/yQnl0ZUFycmF5T3V0cHV0U3RyZWFt1tDQtMjryv2+3cqxo6zI57n7xuTE2rK/tcRieXRlyv3X6bXEyqPT4L/VvOSyu8Tcubu05rSi0OjSqtC0yOu1xMr9vt2jrMTHw7TEx8O0y/y74c2ouf2199PDxNqyv7XEZW5zdXJlQ2FwYWNpdHk8YnIgLz4NCre9t6i21MbkxNqyv86su6S1xGJ5dGXK/dfpvfjQ0MCpyN3S1LTmtKLL+dPQ0qrQtMjrtcTK/b7do6zL+dLUsrux2LWj0MTG5MTasr+1xGJ5dGXK/dfpzKvQobW81sK1xEluZGV4T3V0T2ZCb3VuZHNFeGNlcHRpb27WrsDgtcTS7LOjoaM8YnIgLz4NCtLUz8LKx0J5dGVBcnJheUlucHV0U3RyZWFtILrNIEJ5dGVBcnJheU91dHB1dFN0cmVhbbXEtPrC68asts7KvsD9o7o8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;">private static void testByteArrayInputOutStream(){ byte bytes = I am iSpring.getBytes(); ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte buf = new byte; int length = 0; try{ while((length = bais.read(buf)) > 0){ baos.write(buf, 0, length); } System.out.println(baos.toString(UTF-8)); bais.close(); baos.close(); }catch(IOException e){ e.printStackTrace(); } }
在上面的例子中,我們通過字符串獲取字節數組將其作為ByteArrayInputStream的數據流來源,然后通過讀取ByteArrayInputStream的數據,將讀到的數據寫入到ByteArrayOutputStream中。
FileInputStream & FileOutputStream
FileInputStream 能夠將文件作為數據源,讀取文件中的流,通過File對象或文件路徑等初始化,在其構造函數中,如果傳入的File對象(或與其相對應的文件路徑所表示的File對象)不存在或是一個目錄而不是文件或者由于其他原因無法打開讀取數據,都會導致在初始化階段導致拋出FileNotFoundException異常;與FileInputStream 相對應的是FileOutputStream,可以通過FileOutputStream向文件中寫入數據,也需要通過File對象或文件路徑對其初始化,如同FileInputStream ,如果傳入的File對象(或與其相對應的文件路徑所表示的File對象)是一個目錄而不是文件或者由于其他原因無法創建該文件寫入數據,都會導致在初始化階段拋出FileNotFoundException異常。
以下是FileInputStream 和 FileOutputStream的代碼示例片段:private static void testFileInputOutStream(){ try{ String inputFileName = D:\iWork\file1.txt; String utputFileName = D:\iWork\file2.txt; FileInputStream fis = new FileInputStream(inputFileName); FileOutputStream fos = new FileOutputStream(outputFileName); byte buf = new byte; int length = 0; while ((length = fis.read(buf)) > 0){ fos.write(buf, 0, length); } fis.close(); fos.close(); }catch (FileNotFoundException e){ e.printStackTrace(); }catch (IOException e){ e.printStackTrace(); } }
在上面的例子中,我們通過FileInputStream的read方法讀取file1.txt中的數據,然后將獲得的字節數據通過FileOutputStream的write方法將其寫入到另一個文件file2.txt中,這樣就實現了文件的拷貝,即將file1.txt拷貝到file2.txt。如果file2.txt已經存在,那么在初始FileOutputStream時,可以傳入一邊boolean變量append表示是向已有文件中追加寫入數據還是覆蓋已有數據。
PipedInputStream & PipedOutputStream
PipedInputStream和PipedOutputStream一般是結合使用的,這兩個類用于在兩個線程間進行管道通信,一般在一個線程中執行PipedOutputStream 的write操作,而在另一個線程中執行PipedInputStream的read操作。可以在構造函數中傳入相關的流將PipedInputStream 和PipedOutputStream 綁定起來,也可以通過二者的connect方法將二者綁定起來,一旦二者進進行了綁定,那么PipedInputStream的read方法就會自動讀取PipedOutputStream寫入的數據。PipedInputStream的read操作是阻塞式的,當執行PipedOutputStream的write操作時,PipedInputStream會在另一個線程中自動讀取PipedOutputStream寫入的內容,如果PipedOutputStream一直沒有執行write操作寫入數據,那么PipedInputStream的read方法會一直阻塞PipedInputStream的read方法所運行的線程直至讀到數據。單獨使用PipedInputStream或單獨使用PipedOutputStream時沒有任何意義的,必須將二者通過connect方法(或在構造函數中傳入對應的流)進行連接綁定,如果單獨使用其中的某一個類,就會觸發IOException: Pipe Not Connected.
以下是PipedInputStream和PipedOutputStream的代碼示例片段:
WriterThread類import java.io.*;public class WriterThread extends Thread { PipedOutputStream pos = null; public WriterThread(PipedOutputStream pos){ this.pos = pos; } @Override public void run() { String message = 這條信息來自于WriterThread.; try{ byte bytes = message.getBytes(UTF-8); System.out.println(WriterThread發送信息); this.pos.write(bytes); this.pos.close(); }catch (IOException e){ e.printStackTrace(); } }}
ReaderThread類import java.io.*;public class ReaderThread extends Thread { private PipedInputStream pis = null; public ReaderThread(PipedInputStream pis){ this.pis = pis; } @Override public void run() { byte buf = new byte; try{ System.out.println(ReaderThread阻塞式的等待接收數據...); int length = pis.read(buf); System.out.println(ReaderThread接收到如下信息:); String message = new String(buf, 0, length, UTF-8); System.out.println(message); pis.close(); }catch(IOException e){ e.printStackTrace(); } }}
測試代碼private static void testPipedInputOutputStream(){ try{ PipedInputStream pis = new PipedInputStream(); PipedOutputStream pos = new PipedOutputStream(); pos.connect(pis); WriterThread writerThread = new WriterThread(pos); ReaderThread readerThread = new ReaderThread(pis); readerThread.start(); writerThread.start(); }catch (IOException e){ e.printStackTrace(); } }
在上面的實例中,我們創建了兩個線程類WriterThread和ReaderThread,在WriterThread的構造函數中我們傳入了一個PipedOutputStream,并在線程執行run方法時向WriterThread中寫入數據;在ReaderThread的構造函數中我們傳入了一個PipedInputStream,在其線程執行run方法時阻塞式的執行read操作,等待獲取數據。我們通過pos.connect(pis)將這兩種流綁定在一起,最后分別執行線程ReaderThread和WriterThread。
輸出結果如下:
我們可以看到即使我們先執行了ReaderThread線程,ReaderThread中的PipedInputStream還是一直在阻塞式的等待數據的到來。
ObjectInputStream & ObjectOutputStream
ObjectOutputStream具有一系列writeXXX方法,在其構造函數中可以摻入一個OutputStream,可以方便的向指定的輸出流中寫入基本類型數據以及String,比如writeBoolean、writeChar、writeInt、writeLong、writeFloat、writeDouble、writeCharts、writeUTF等,除此之外,ObjectOutputStream還具有writeObject方法。writeObject方法中傳入的類型必須實現了Serializable接口,從而在執行writeObject操作時將對象進行序列化成流,并將其寫入指定的輸出流中。與ObjectOutputStream相對應的是ObjectInputStream,ObjectInputStream有與OutputStream中的writeXXX系列方法完全對應的readXXX系列方法,專門用于讀取OutputStream通過writeXXX寫入的數據。
以下是ObjectInputStream 和 ObjectOutputStream的示例代碼:
Person類import java.io.Serializable;public class Person implements Serializable { private String name = ; private int age = 0; public Person(String name, int age){ this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; }}
測試代碼private static void testObjectInputOutputStream(){ try{ String fileName = D:\iWork\file.tmp; //將內存中的對象序列化到物理文件中 FileOutputStream fos = new FileOutputStream(fileName); ObjectOutputStream os = new ObjectOutputStream(fos); String description = 以下是人員數組; Person persons = new Person{ new Person(iSpring, 26), new Person(Mr.Sun, 27), new Person(Miss.Zhou, 27) }; oos.writeObject(description); oos.writeInt(persons.length); for(Person person : persons){ oos.writeObject(person); } oos.close(); //從物理文件中反序列化讀取對象信息 FileInputStream fis = new FileInputStream(fileName); ObjectInputStream is = new ObjectInputStream(fis); String str = (String)ois.readObject(); System.out.println(str); int personCount = ois.readInt(); for(int i = 0; i < personCount; i++){ Person person = (Person)ois.readObject(); StringBuilder sb = new StringBuilder(); sb.append(姓名: ).append(person.getName()).append(, 年齡: ).append(person.getAge()); System.out.println(sb); } }catch (FileNotFoundException e){ e.printStackTrace(); }catch(IOException e){ e.printStackTrace(); }catch (ClassNotFoundException e){ e.printStackTrace(); } }
輸出結果如下:
Person實現了Serializable接口,需要注意的是,Serializable接口是一個標識接口,并不需要實現任何方法。我們首先通過ObjectOutputStream,將Person等數組信息序列化成流,然后通過調用writeObject等方法將其寫入到FileOutputStream中,從而實現了將內存中的基本類型和對象序列化保存到硬盤的物理文件中。然后通過FileInputStream讀取文件,將文件的輸入流傳入到到ObjectInputStream的構造函數中,這樣ObjectInputStream就可以通過執行對應的readXXX操作讀取基本類型或對象。當執行readObject操作時,返回的是Object類型,需要強制轉換為對應的實際類型。需要注意的是,ObjectInputStream執行readXXX操作的方法順序需要與ObjectOutputStream執行writeXXX操作的方法順序一致,否則就會讀到錯誤的數據或拋出異常,比如一開始向FileOutputStream中執行writeFloat,而在FileInputStream中首先執行了readInt操作,不會報錯,因為writeFloat寫入了4個字節的數據,readInt讀入了4個字節的數據,雖然可以將該Float轉換為對應的int,但是其實已經不是我們想要的數據了,所以要注意readXXX操作與writeXXX操作執行順序的對應。
SequenceInputStream
SequenceInputStream 主要是將兩個(或多個)InputStream在邏輯上合并為一個InputStream,比如在構造函數中傳入兩個InputStream,分別為in1和in2,那么SequenceInputStream在讀取操作時會先讀取in1,如果in1讀取完畢,就會接著讀取in2。在我們理解了SequenceInputStream 的作用是將兩個輸入流合并為一個輸入流之后,我們就能理解為什么不存在對應的SequenceOutputStream 類了,因為將一個輸出流拆分為多個輸出流是沒有意義的。
以下是關于SequenceInputStream的示例代碼:private static void testSequenceInputOutputStream(){ String inputFileName1 = D:\iWork\file1.txt; String inputFileName2 = D:\iWork\file2.txt; String utputFileName = D:\iWork\file3.txt; try{ FileInputStream fis1 = new FileInputStream(inputFileName1); FileInputStream fis2 = new FileInputStream(inputFileName2); SequenceInputStream sis = new SequenceInputStream(fis1, fis2); FileOutputStream fos = new FileOutputStream(outputFileName); byte buf = new byte; int length = 0; while((length = sis.read(buf)) > 0){ fos.write(buf, 0, length); } sis.close(); fos.close(); }catch (FileNotFoundException e){ e.printStackTrace(); }catch (IOException e){ e.printStackTrace(); } }
我們通過FileInputStream分別獲取了file1.txt和file2.txt的輸入流,然后將這兩個輸入流作為構造函數的參數創建了SequenceInputStream 的實例,所以該SequenceInputStream 中已經在邏輯上將file1.txt和file2.txt的內容合并為了一個輸入流,然后我們讀取該SequenceInputStream 中的數據,并將讀到的數據寫入到一個新的FileOutputStream中,這樣我們就實現了將file1.txt和file2.txt合并為一個新的文件file3.txt,原有的file1.txt和file2.txt文件不受任何影響。
StringBufferInputStream
StringBufferInputStream允許通過在構造函數中傳入字符串以讀取字節,在讀取時內部主要調用了String的charAt方法。與SequenceInputStream類似,StringBufferInputStream也沒有對應的OutputStream,即不存在StringBufferOutputStream類。Java沒有設計StringBufferOutputStream類的理由也很簡單,我們假設StringBufferOutputStream存在,那么StringBufferOutputStream應該是內部通過執行write操作寫入數據更新其內部的String對象,比如有可能是通過StringBuilder來實現,但是這樣做毫無意義,因為一旦我們String的構造函數中可以直接傳入字節數組構建字符串,簡單明了,所以設計StringBufferOutputStream就沒有太大的必要了。StringBufferInputStream這個類本身存在一點問題,它不能很好地將字符數組轉換為字節數組,所以該類被Java標記為廢棄的(Deprecated),其官方推薦使用StringReader作為代替。
以下是關于StringBufferInputStream的示例代碼:private static void testStringBufferInputStream(){ String message = I am iSpirng.; StringBufferInputStream sbis = new StringBufferInputStream(message); byte buf = new byte; try{ int length = sbis.read(buf); if(length > 0){ System.out.println(new String(buf, 0, length, UTF-8)); } sbis.close(); }catch (IOException e){ e.printStackTrace(); } }
輸出結果如下:
FilterInputStream & FilterOutputStream
FilterInputStream包含了其他的輸入流,說具體點就是在其構造函數中需要傳入一個InputStream并將其保存在其名為in的字段中,FilterInputStream只是簡單的覆蓋了所有的方法,之所說是簡單覆蓋是因為在每個覆蓋函數中,它只是調用內部的保存在in字段中的InputStream所對應的方法,比如在其覆蓋read方法時,內部只是簡單調用了in.read()方法。FilterInputStream的子類可以進一步覆蓋某些方法以保持接口不變的情況下實現某一特性(比如其子類有的可以通過使用緩存優化讀取的效率)或者提供一些其他額外的實用方法。所以在使用時FilterInputStream可以讓傳入的InputStream具有一些額外的特性,即對構造函數傳入的InputStream進行了一層包裹,使用了典型的裝飾著模式,如果只看FilterInputStream本身這一個類的話,則該類自己本身意義不大,因為其只是通過內部的字段in簡單覆寫某些方法。但是如果將FilterInputStream 和其子類結合起來使用話,那么就很有用了。比如FilterInputStream 有兩個子類BufferedInputStream和DataInputStream,這兩個類在下面還會詳細介紹。BufferedInputStream對read操作做了優化,每次讀操作時都讀取一大塊數據,然后將其放入內部維護的一個字節數組緩沖區中。當外面調用BufferedInputStream的read方法時,首先去該緩沖區中讀取數據,這樣就避免了頻繁的實際的讀操作,BufferedInputStream對外沒有暴露額外的其他方法,但是其內部的read方法已經經過優化了,所以在執行讀操作的時候效率更高。DataInputStream與ObjectInputStream有點類似,可以通過一些readXXX方法讀取基本類型的數據,這是非常有用的一些方法。假設我們即想使用BufferedInputStream讀取效率高的特性,又想是想DataInputStream中的readXXX方法怎么辦呢?很簡單,如下代碼所示:InputStream is = getInputStreamBySomeway();BufferedInputStream bis = new BufferedInputStream(is);DataInputStream dis = new DataInputStream(bis);
然后我們就可以調用dis.readXXX()等方法,即快又方便,這就是FilterInputStream子類通過構造函數層層傳遞結合在一起使用多種特性的魅力。與之相對應的是BufferedOutputStream和DataOutputStream,BufferedOutputStream優化了write方法,提高了寫的效率,DataOutputStream具有很多writeXXX方法,可以方便的寫入基本類型數據。如果想使用writeXXX方法,還想提高寫入到效率,可以如下代碼使用,與上面的代碼差不多:OutputStream s = getOutputStreamBySomeway();BufferedOutputStream bos = new BufferedOutputStream();DataOutputStream dos = new DataOutputStream(bos);
然后在調用dos.writeXXX方法時效率就已經提高了。
BufferedInputStream & BufferedOutputStream
如上面所介紹的那樣,在BufferedInputStream的構造函數中需要傳入一個InputStream, BufferedInputStream內部有一個字節數組緩沖區,每次執行read操作的時候就從這buf中讀取數據,從buf中讀取數據沒有多大的開銷。如果buf中已經沒有了要讀取的數據,那么就去執行其內部綁定的InputStream的read方法,而且是一次性讀取很大一塊數據,以便填充滿buf緩沖區。緩沖區buf的默認大小是8192字節,也就是8K,在構造函數中我們也可以自己傳入一個size指定緩沖區的大小。由于我們在執行BufferedInputStream的read操作的時候,很多時候都是從緩沖區中讀取的數據,這樣就大大減少了實際執行其指定的InputStream的read操作的次數,也就提高了讀取的效率。與BufferedInputStream 相對的是BufferedOutputStream。在BufferedOutputStream的構造函數中我們需要傳入一個OutputStream,這樣就將BufferedOutputStream與該OutputStream綁定在了一起。BufferedOutputStream內部有一個字節緩沖區buf,在執行write操作時,將要寫入的數據先一起緩存在一起,將其存入字節緩沖區buf中,buf是有限定大小的,默認的大小是8192字節,即8KB,當然也可以在構造函數中傳入size指定buf的大小。該buf只要被指定了大小之后就不會自動擴容,所以其是有限定大小的,既然有限定大小,就會有被填充完的時刻,當buf被填充完畢的時候會調用BufferedOutputStream的flushBuffer方法,該方法會通過調用其綁定的OutputStream的write方法將buf中的數據進行實際的寫入操作并將buf的指向歸零(可以看做是將buf中的數據清空)。如果想讓緩存區buf中的數據理解真的被寫入OutputStream中,可以調用flush方法,flush方法內部會調用flushBuffer方法。由于buf的存在,會大大減少實際執行OutputStream的write操作的次數,優化了寫的效率。
以下是BufferedInputStream 和 BufferedOutputStream的示例代碼片段:private static void testBufferedInputOutputStream(){ try{ String inputFileName = D:\iWork\file1.txt; String utputFileName = D:\iWork\file2.txt; FileInputStream fis = new FileInputStream(inputFileName); BufferedInputStream bis = new BufferedInputStream(fis, 1024 * 10); FileOutputStream fos = new FileOutputStream(outputFileName); BufferedOutputStream bos = new BufferedOutputStream(fos, 1024 * 10); byte buf = new byte; int length = 0; while ((length = bis.read(buf)) > 0){ bos.write(buf, 0, length); } bis.close(); bos.close(); }catch (FileNotFoundException e){ e.printStackTrace(); }catch (IOException e){ e.printStackTrace(); } }
上面的代碼將從file1.txt讀取文件輸入流,然后將讀到的數據寫入到file2.txt中,即實現了將file1.txt拷貝到file2.txt中。其實不通過BufferedInputStream 和 BufferedOutputStream也可以完成這樣的工作,使用這個兩個類的好處是,可以對file1.txt的讀取以及file2.txt的寫入提高效率,從而提升文件拷貝的效率。
內容由用戶發布,不代表本站觀點。如發現有害或侵權內容。請點擊這里