深刻分析 Java I/O 的工做機制(二)

磁盤 I/O 工做機制緩存

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

咱們知道數據在磁盤的惟一最小描述就是文件,也就是說上層應用程序只能經過文件來操做磁盤上的數據,文件也是操做系統和磁盤驅動器交互的一個最小單 元。值得注意的是 Java 中一般的 File 並不表明一個真實存在的文件對象,當你經過指定一個路徑描述符時,它就會返回一個表明這個路徑相關聯的一個虛擬對象,這個多是一個真實存在的文件或者是 一個包含多個文件的目錄。爲什麼要這樣設計?由於大部分狀況下,咱們並不關心這個文件是否真的存在,而是關心這個文件到底如何操做。例如咱們手機裏一般存了 幾百個朋友的電話號碼,可是咱們一般關心的是我有沒有這個朋友的電話號碼,或者這個電話號碼是什麼,可是這個電話號碼到底能不能打通,咱們並非時時刻刻 都去檢查,而只有在真正要給他打電話時纔會看這個電話能不能用。也就是使用這個電話記錄要比打這個電話的次數多不少。數據結構

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

下面以清單 1 的程序爲例,介紹下如何從磁盤讀取一段文本字符。以下圖所示:工具


圖 7. 從磁盤讀取文件
圖 7. 從磁盤讀取文件 操作系統

當傳入一個文件路徑,將會根據這個路徑建立一個 File 對象來標識這個文件,而後將會根據這個 File 對象建立真正讀取文件的操做對象,這時將會真正建立一個關聯真實存在的磁盤文件的文件描述符 FileDescriptor,經過這個對象能夠直接控制這個磁盤文件。因爲咱們須要讀取的是字符格式,因此須要 StreamDecoder 類將 byte 解碼爲 char 格式,至於如何從磁盤驅動器上讀取一段數據,由操做系統幫咱們完成。至於操做系統是如何將數據持久化到磁盤以及如何創建數據結構須要根據當前操做系統使用 何種文件系統來回答,至於文件系統的相關細節能夠參考另外的文章。設計

 

Java Socket 的工做機制code

Socket 這個概念沒有對應到一個具體的實體,它是描述計算機之間完成相互通訊一種抽象功能。打個比方,能夠把 Socket 比做爲兩個城市之間的交通工具,有了它,就能夠在城市之間來回穿梭了。交通工具備多種,每種交通工具也有相應的交通規則。Socket 也同樣,也有多種。大部分狀況下咱們使用的都是基於 TCP/IP 的流套接字,它是一種穩定的通訊協議。對象

下圖是典型的基於 Socket 的通訊的場景:接口


圖 8.Socket 通訊示例
圖 8.Socket 通訊示例 

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

創建通訊鏈路

當客戶端要與服務端通訊,客戶端首先要建立一個 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 部分將介紹避免這種狀況。

相關文章
相關標籤/搜索