Java IO工做機制分析

  Java的IO類都在java.io包下,這些類大體可分爲如下4種:java

  1. 基於字節操做的 I/O 接口:InputStream 和 OutputStream
  2. 基於字符操做的 I/O 接口:Writer 和 Reader
  3. 基於磁盤操做的 I/O 接口:File
  4. 基於網絡操做的 I/O 接口:Socket

1 IO類庫的基本結構

1.1 基於字節操做的IO接口

  基於字節操做的IO接口分別是InputStream和OutputStream,InputStream的類結構圖以下所示:網絡

 

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

   關於各個子類的使用能夠參考JDK 的 API 說明文檔,這裏咱們須要注意的是:操做數據的方式是能夠組合的,以下所示:異步

InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("h:\\haha.txt"), "utf-8");

  從上面代碼能夠看出,InputStreamReader能夠從FileInputStream中讀取數據,從源碼中能夠看出來,其實不只是InputStreamReader,全部的IO類均可以以這種方式來組合使用。編碼

  還有一點須要注意的是必須制定流最終寫入到什麼地方,是磁盤仍是網絡,從OutputStream類圖中能夠看出,寫入網絡中實際也是寫文件操做,只不過底層是經過網絡傳輸了。spa

1.2 基於字符的IO操做接口

   不論是磁盤仍是網絡數據傳輸,都是以字節爲單位的,可是程序中通常常見的數據操做都是以字符爲單位的(Java中char佔用2字節,C/C++中 char佔用1字節),這就須要咱們有一個操做字符的IO接口,來處理字符與字節見的編碼轉換問題,也就是Write和Reader接口及其實現類,他們 兩者的類接口圖以下:操作系統

 

  讀字符接口Reader的最主要操做方法爲read(),其讀取字符並返回讀取的字符數,不論是 Writer 仍是 Reader 類它們都只定義了讀取或寫入的數據字符的方式,也就是怎麼寫或讀,可是並無規定數據要寫到哪去(好比磁盤或者網絡)。線程

1.3 字節與字符的轉化接口

  有時數據持久化和網絡傳輸是以字節進行的,全部須要字節和字符之間的相互轉換。code

圖 5. 字符解碼相關類結構 圖 6. 字符編碼相關類結構

/**
 * 使用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();
    }
}

 

參考資料:

  一、深刻分析 Java I/O 的工做機制

  二、Java IO系統

相關文章
相關標籤/搜索