Java I/O 工做機制(一) —— Java 的 I/O 類庫的基本架構

Java 的 I/O 類庫的基本架構

Java 的 I/O 操做類在包 java.io 下,有將近 80 個類。css

按數據格式分類:前端

  • 面向字節(Byte)操做的 I/O 接口:InputStream 和 OutputStream
  • 面向字符(Character)操做的 I/O 接口:Writer 和 Reader

按做用位置分類:java

  • 基於磁盤操做的 I/O 接口:File
  • 基於網絡操做的 I/O 接口:Socket(不在java.io中)

1. IO數據格式

(1)面向字節:操做以8位爲單位對二進制數據進行操做,不對數據進行轉換。這些類都是InputStream 和 OutputStream的子類。以InputStream/OutputStream爲後綴的類都是字節流,能夠處理全部類型的數據。數據庫

(2)面向字符:操做以字符爲單位,讀時將二進制數據轉換爲字符,寫時將字符轉換爲二進制數據Writer 和 Reader的子類,以Writer/Reader爲後綴的都是字符流。後端

硬盤上全部的文件都是以字節形式保存,字符只在內存中才會造成。即只在處理純文本文件時,優先考慮使用字符流,除此以外都用字節流。瀏覽器

 

InputStream 相關類層次結構:(OutputStream相似)

 

Writer 相關類層次結構:(Reader相似)

其中:緩存

字符流:服務器

  • InputStreamReader/OutputStreamWriter 是字節流轉化爲字符流的橋轉換器。
  • BufferReader/BufferWriter 逐行讀寫流,可用於較大的文本文件。是過濾流,須要用其餘的節點流作參數構造對象。

字節流:網絡

  • FileInputStream/FileOutputStream 文件輸入輸出流。
  • PipedInputStream/PipedOutputStream 管道里,線程交互時使用。
  • ObjectInputStream/ObjectOutputStream 對象流,實現對象序列化

讀寫操做實例:數據結構

/**
 * 使用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進行讀取文件,而後用OutputStreamWriter寫入文件
 */
@Test
public void testInputStreamReader() throws IOException {
    //操做數據的方式是能夠組合的,此處FileInputStream讀出的字節流用InputStreamReader轉化爲了字符流對象
    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(); } @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); }

FileReader類繼承了InputStreamReader,FileReader讀取文件流,經過StreamDecoder解碼成char,其解碼字符集使用的是默認字符集。在Java中,咱們應該使用File對象來判斷某個文件是否存在,若是咱們用FileOutputStream或者FileWriter打開,那麼它確定會被覆蓋。

2. IO發生位置

(1) 磁盤IO工做機制

前面介紹了基本的 Java I/O 的操做接口,這些接口主要定義瞭如何操做數據,以及介紹了操做兩種數據結構:字節和字符的方式。還有一個關鍵問題就是數據寫到何處,其中一個主要方式就是將數據持久化到物理磁盤,下面將介紹如何將數據持久化到物理磁盤的過程。

數據在磁盤的惟一最小描述是文件,也就是說上層應用程序只能經過文件來操做磁盤上的數據,文件也是操做系統和磁盤驅動器交互的一個最小單元。值得注意的是 Java 中一般的 File 並不表明一個真實存在的文件對象,當你經過指定一個路徑描述符時,它就會返回一個表明這個路徑相關聯的一個虛擬對象,這個多是一個真實存在的文件或者是一個包含多個文件的目錄。

什麼時候真正會要檢查一個文件存不存?就是在真正要讀取這個文件時,例如 FileInputStream 類都是操做一個文件的接口,注意到在建立一個 FileInputStream 對象時,會建立一個 FileDescriptor 對象,其實這個對象就是真正表明一個存在的文件對象的描述,當咱們在操做一個文件對象時能夠經過 getFD() 方法獲取真正操做的與底層操做系統關聯的文件描述。例如能夠調用 FileDescriptor.sync() 方法將操做系統緩存中的數據強制刷新到物理磁盤中。

從磁盤讀取文件過程:

當傳入一個文件路徑,將會根據這個路徑建立一個 File 對象來標識這個文件,而後將會根據這個 File 對象建立真正讀取文件的操做對象,這時將會真正建立一個關聯真實存在的磁盤文件的文件描述符 FileDescriptor,經過這個對象能夠直接控制這個磁盤文件。因爲咱們須要讀取的是字符格式,因此須要 StreamDecoder 類將 byte 解碼爲 char 格式,至於如何從磁盤驅動器上讀取一段數據,由操做系統幫咱們完成。

 

(2)網絡IO工做機制(Socket)

Socket 描述計算機之間完成相互通訊一種抽象功能。Socket 也同樣有多種,大部分狀況下咱們使用的都是基於 TCP/IP 的流套接字,它是一種穩定的通訊協議。

下典型的基於 Socket 的通訊的場景:

主機 A 的應用程序要能和主機 B 的應用程序通訊,必須經過 Socket 創建鏈接,而創建 Socket 鏈接必須須要底層 TCP/IP 協議來創建 TCP 鏈接。創建 TCP 鏈接須要底層 IP 協議來尋址網絡中的主機。咱們知道網絡層使用的 IP 協議能夠幫助咱們根據 IP 地址來找到目標主機,可是一臺主機上可能運行着多個應用程序,如何才能與指定的應用程序通訊就要經過 TCP 或 UPD 的地址也就是端口號來指定。這樣就能夠經過一個 Socket 實例惟一表明一個主機上的一個應用程序的通訊鏈路了。

(TCP/UDP:找端口號,從而與應用程序通訊。IP:找主機)

創建通訊鏈路

當客戶端要與服務端通訊,客戶端首先要建立一個 Socket 實例,操做系統將爲這個 Socket 實例分配一個沒有被使用的本地端口號,並建立一個包含本地和遠程地址和端口號的套接字數據結構,這個數據結構將一直保存在系統中直到這個鏈接關閉。在建立 Socket 實例的構造函數正確返回以前,將要進行 TCP 的三次握手協議,TCP 握手協議完成後,Socket 實例對象將建立完成,不然將拋出 IOException 錯誤。

與之對應的服務端將建立一個 ServerSocket 實例,ServerSocket 建立比較簡單隻要指定的端口號沒有被佔用,通常實例建立都會成功,同時操做系統也會爲 ServerSocket 實例建立一個底層數據結構,這個數據結構中包含指定監聽的端口號和包含監聽地址的通配符,一般狀況下都是「*」即監聽全部地址。以後當調用 accept() 方法時,將進入阻塞狀態,等待客戶端的請求。當一個新的請求到來時,將爲這個鏈接建立一個新的套接字數據結構,該套接字數據的信息包含的地址和端口信息正是請求源地址和端口。這個新建立的數據結構將會關聯到 ServerSocket 實例的一個未完成的鏈接數據結構列表中,注意這時服務端與之對應的 Socket 實例並無完成建立,而要等到與客戶端的三次握手完成後,這個服務端的 Socket 實例纔會返回,並將這個 Socket 實例對應的數據結構從未完成列表中移到已完成列表中。因此 ServerSocket 所關聯的列表中每一個數據結構,都表明與一個客戶端的創建的 TCP 鏈接

數據傳輸

傳輸數據是咱們創建鏈接的主要目的,如何經過 Socket 傳輸數據:

當鏈接已經創建成功,服務端和客戶端都會擁有一個 Socket 實例,每一個 Socket 實例都有一個 InputStream 和 OutputStream,正是經過這兩個對象來交換數據。同時咱們也知道網絡 I/O 都是以字節流傳輸的。當 Socket 對象建立時,操做系統將會爲 InputStream 和 OutputStream 分別分配必定大小的緩衝區,數據的寫入和讀取都是經過這個緩存區完成的。寫入端將數據寫到 OutputStream 對應的 SendQ 隊列中,當隊列填滿時,數據將被髮送到另外一端 InputStream 的 RecvQ 隊列中,若是這時 RecvQ 已經滿了,那麼 OutputStream 的 write 方法將會阻塞直到 RecvQ 隊列有足夠的空間容納 SendQ 發送的數據。值得特別注意的是,這個緩存區的大小以及寫入端的速度和讀取端的速度很是影響這個鏈接的數據傳輸效率,因爲可能會發生阻塞,因此網絡 I/O 與磁盤 I/O 在數據的寫入和讀取還要有一個協調的過程,若是兩邊同時傳送數據時可能會產生死鎖,在後面 NIO 部分將介紹避免這種狀況。

3. IO調優

提高磁盤 I/O 性能一般的方法:

  1. 增長緩存,減小磁盤訪問次數
  2. 優化磁盤的管理系統,設計最優的磁盤訪問策略,以及磁盤的尋址策略(在底層操做系統層面考慮)
  3. 設計合理的磁盤存儲數據塊,以及訪問這些數據塊的策略(在應用層面考慮)。如咱們能夠給存放的數據設計索引,經過尋址索引來加快和減小磁盤的訪問,還有能夠採用異步和非阻塞的方式加快磁盤的訪問效率。
  4. 應用合理的 RAID 策略提高磁盤 IO

網絡 I/O 優化一般有一些基本處理原則:

  1. 減小網絡交互次數:1)在須要網絡交互的兩端會設置緩存,好比 Oracle 的 JDBC 驅動程序提供了對查詢的 SQL 結果的緩存,在客戶端和數據庫端都有,能夠有效的減小對數據庫的訪問。2)合併訪問請求:如在查詢數據庫時,咱們要查 10 個 id,我能夠每次查一個 id,也能夠一次查 10 個 id。再好比在訪問一個頁面時經過會有多個 js 或 css 的文件,咱們能夠將多個 js 文件合併在一個 HTTP 連接中,每一個文件用逗號隔開,而後發送到後端 Web 服務器根據這個 URL 連接,再拆分出各個文件,而後打包再一併發回給前端瀏覽器。這些都是經常使用的減小網絡 I/O 的辦法。
  2. 減小網絡傳輸數據量的大小:減小網絡數據量的辦法一般是將數據壓縮後再傳輸,如 HTTP 請求中,一般 Web 服務器將請求的 Web 頁面 gzip 壓縮後在傳輸給瀏覽器。還有就是經過設計簡單的協議,儘可能經過讀取協議頭來獲取有用的價值信息。
  3. 儘可能減小編碼:一般在網絡 I/O 中數據傳輸都是以字節形式的,也就是一般要序列化。可是咱們發送要傳輸的數據都是字符形式的,從字符到字節必須編碼。可是這個編碼過程是比較耗時的,因此在要通過網絡 I/O 傳輸時,儘可能直接以字節形式發送。也就是儘可能提早將字符轉化爲字節,或者減小字符到字節的轉化過程。
  4. 根據應用場景設計合適的交互方式:所謂的交互場景主要包括同步與異步、阻塞與非阻塞方式。

 

參考連接:https://www.ibm.com/developerworks/cn/java/j-lo-javaio/

相關文章
相關標籤/搜索