Java的IO類都在java.io包下,這些類大致可分為以下4種:
- 基於字節操作的 I/O 接口:InputStream 和 OutputStream
- 基於字符操作的 I/O 接口:Writer 和 Reader
- 基於磁盤操作的 I/O 接口:File
- 基於網絡操作的 I/O 接口:Socket
1 IO類庫的基本結構
1.1 基於字節操作的IO接口
基於字節操作的IO接口分別是InputStream和OutputStream,InputStream的類結構圖如下所示:

同InputStream類似,OutputStream類也有着相同的類結構圖。

關於各個子類的使用可以參考JDK 的 API 說明文檔,這里我們需要注意的是:操作數據的方式是可以組合的,如下所示:
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("h:\\haha.txt"), "utf-8");
從上面代碼可以看出,InputStreamReader可以從FileInputStream中讀取數據,從源碼中可以看出來,其實不僅是InputStreamReader,所有的IO類都可以以這種方式來組合使用。

還有一點需要注意的是必須制定流最終寫入到什么地方,是磁盤還是網絡,從OutputStream類圖中可以看出,寫入網絡中實際也是寫文件操作,只不過底層是通過網絡傳輸了。
1.2 基於字符的IO操作接口
不管是磁盤還是網絡數據傳輸,都是以字節為單位的,但是程序中一般常見的數據操作都是以字符為單位的(Java中char占用2字節,C/C++中 char占用1字節),這就需要我們有一個操作字符的IO接口,來處理字符與字節見的編碼轉換問題,也就是Write和Reader接口及其實現類,他們 二者的類接口圖如下:


讀字符接口Reader的最主要操作方法為read(),其讀取字符並返回讀取的字符數,不管是 Writer 還是 Reader 類它們都只定義了讀取或寫入的數據字符的方式,也就是怎么寫或讀,但是並沒有規定數據要寫到哪去(比如磁盤或者網絡)。
1.3 字節與字符的轉化接口
有時數據持久化和網絡傳輸是以字節進行的,所有需要字節和字符之間的相互轉換。

/** * 使用FileReader進行讀取文件 */ @Test public void testFileReader() throws IOException { FileReader fileReader = new FileReader("h:\\haha.txt"); char[] buff = new char[512]; StringBuffer stringBuffer = new StringBuffer(); while (fileReader.read(buff) > 0) { stringBuffer.append(buff); } fileReader.close(); System.out.print(stringBuffer.toString()); } /** * 使用FileReader進行讀取文件,然后FileWriter寫入另一個文件 */ @Test public void testFileReaderAndFileWriter() throws IOException { FileReader fileReader = new FileReader("h:\\haha.txt"); char[] buff = new char[512]; StringBuffer stringBuffer = new StringBuffer(); while (fileReader.read(buff) > 0) { stringBuffer.append(buff); } System.out.println(stringBuffer.toString()); FileWriter fileWriter = new FileWriter("h:\\haha2.txt"); fileWriter.write(stringBuffer.toString().trim()); fileWriter.close(); System.out.println("寫入文件成功"); } /** * 使用InputStreamReader進行讀取文件 */ @Test public void testInputStreamReader() throws IOException { InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("h:\\haha.txt"), "utf-8"); char[] buff = new char[512]; StringBuffer stringBuffer = new StringBuffer(); while (inputStreamReader.read(buff) > 0) { stringBuffer.append(buff); } System.out.println(stringBuffer.toString()); } @Test public void testIntputStream2() throws IOException { InputStreamReader inputStreamReader = new InputStreamReader(new StringBufferInputStream("hello world")); char[] buff = new char[512]; int n = inputStreamReader.read(buff); System.out.println(n); System.out.println(buff); } /** * 使用inputStreamReader進行讀取文件,然后OutputStreamWriter寫入另一個文件 */ @Test public void testOutputStreamWriter() throws IOException { InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("h:\\haha.txt"), "utf-8"); char[] buff = new char[512]; StringBuffer stringBuffer = new StringBuffer(); while (inputStreamReader.read(buff) > 0) { stringBuffer.append(buff); } System.out.println(stringBuffer.toString()); OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("h:\\haha2.txt"), "utf-8"); outputStreamWriter.write(stringBuffer.toString().trim()); outputStreamWriter.close(); }
注意:FileReader類繼承了InputStreamReader,FileReader讀取文件流,通過StreamDecoder解碼成char,其解碼字符集使用的是默認字符集。在Java中,我們應該使用File對象來判斷某個文件是否存在,如果我們用FileOutputStream或者FileWriter打開,那么它肯定會被覆蓋。
2 同步和異步、阻塞和非阻塞
同步和異步是針對IO來說的。所謂同步就是一個任務的完成需要依賴另外一個任務時,只有等待被依賴的任務完成后,依賴的任務才能算完成,這是一種可靠的任務序列。要么成功都成功,失敗都失敗,兩個任務的狀態可以保持一致。而異步是不需要等待被依賴的任務完成,只是通知被依賴的任務要完成什么工作,依賴的任務也立即執行,只要自己完成了整個任務就算完成了。至於被依賴的任務最終是否真正完成,依賴它的任務無法確定,所以它是不可靠的任務序列。我們可以用打電話和發短信來很好的比喻同步與異步操作。
阻塞和非阻塞是針對CPU來說的。阻塞與非阻塞主要是從 CPU 的消耗上來說的,阻塞就是 CPU 停下來等待一個慢的操作完成 CPU 才接着完成其它的事。非阻塞就是在這個慢的操作在執行時 CPU 去干其它別的事,等這個慢的操作完成時,CPU 再接着完成后續的操作。雖然表面上看非阻塞的方式可以明顯的提高 CPU 的利用率,但是也帶了另外一種后果就是系統的線程切換增加。增加的 CPU 使用時間能不能補償系統的切換成本需要好好評估。
3 序列化
Java的對象序列化將那些實現了Serializable接口的對象轉換成一個字節序列,並能夠在以后將這個字節序列完全恢復為原來的對象。這一過程可通過網絡進行,這樣序列化機制能夠自動彌補不同操作系統之間的差異。對應序列化的聰明之處在於它不僅保存了對象的“全景圖”,而且能夠追蹤到對象自所包含的引用,並保存這些對象;接着又能夠對對象內包含的每個這樣的引用進行最終;以此類推。
要實例化一個對象,首先創建某些OutputStream對象,然后將其封裝在一個ObjectOutputStream對象內,這是,只需要調用writeObject()即可將對象序列化,並將其發送到OutputStream(對象序列化基於字節,因此使用InputStream和OutputStream繼承類層次結構)。反序列化和序列化過程正好相反,需要將一個InputStream封裝在ObjectInputStream內,然后調用readObject()獲取一個引用,它指向一個向上轉型的Object,所以必須向下轉型才能直接設置它們。下面是序列化和反序列化示例代碼:
@Test public void testWriteSerialization() { try { FileOutputStream fileOutputStream = new FileOutputStream("h:\\serialize.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject("start"); objectOutputStream.writeObject(new Person("luoxn28", 23)); objectOutputStream.writeObject(12); objectOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } System.out.println("end..."); } @Test public void testReadSerialization() { try { FileInputStream fileInputStream = new FileInputStream("h:\\serialize.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); String info1 = (String) objectInputStream.readObject(); Person person = (Person) objectInputStream.readObject(); int num = (int) objectInputStream.readObject(); System.out.println(info1); System.out.println(person); System.out.println(num); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
參考資料:
2、Java IO系統
