深入分析Java I/O工作機制 學習筆記

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

Java的I/O操作類在包java.io下,大概有將近80個類,這些類大概可以分成如下四組。

1.基於字節操作的I/O接口:InputStream和OutputStream。

基於字節的I/O操作接口輸入和輸出分別是InputStream和OutputStream,輸入流根據數據類型和操作方式又被劃分成若干個子類,每個子類分別處理不同操作類型,OutputStream的類層次結構也類似。

操作數據的方式是可以組合使用的,如這樣組合使用:

OutputStream out = new BufferedOutputStream(new ObjectOutputStream(new FileOutputStream("fileName"));

流最終寫到什麼地方必須要指定,要麼是寫到磁盤,要麼是寫到網絡中。

寫網絡實際上也是寫文件,只不過寫網絡還有一步需要處理,就是讓底層操作系統再將數據傳送到其他地方而不是本地磁盤。

2.基於字符操作的I/O接口:Writer和Reader

不管是磁盤還是網絡傳輸,最小的存儲單元都是字節,而不是字符,所以I/O操作的都是字節而不是字符。

但是爲什麼有操作字符的I/O接口呢?這是因爲我們的程序中通常操作的數據都是字符形式的,爲了操作方便當然要提供一個直接寫字符的I/O接口。

我們知道字符到字節必須要經過編碼轉換,而這個編碼又非常耗時,而且還會經常出現亂碼問題,所以I/O編碼問題經常是讓人頭疼的問題。

不管是Writer還是Reader類,它們都只定義了讀取或寫入的數據字符的方式,也就是怎麼寫或讀,但是並沒有規定數據要寫到哪去,寫到哪去就是我們後面要討論的基於磁盤和網絡的工作機制。

3.字節和字符的轉化接口

數據持久化或網絡傳輸都是以字節進行的,所以必須要有字符到字節或字節到字符的轉化。InputStreamReader類是字節到字符的轉化橋樑,InputStream到Reader的過程要指定編碼字符集,否則將採用操作系統默認字符集,很可能會出現亂碼問題。StreamDecoder正是完成字節到字符的編碼的實現類。也就是當你用如下方式讀取一個文件時:

try {

StringBuffer str = new StringBuffer();

Char[] buf = new char[1024];

FileReader f = new FileReader("file");

while(f.read(buf)>0) {

str.append(buf);

}

str.toString();

} catch (IOException e) {

}

FileReader類就是按照上面的工作方式讀取文件的,FileReader繼承了InputStreamReader類,實際上是讀取文件流,然後通過StreamDecoder解碼成char,只不過這裏的編碼字符集是默認字符集。

寫入也是類似的過程,通過OutputStreamWriter類完成字符到字節的編碼過程,由StreamEncoder完成編碼過程。

基於磁盤操作的I/O接口:File

基於網絡操作的I/O接口:Socket

前兩組主要是傳輸數據的格式,後兩組主要是傳輸數據的方式,雖然Socket類並不在java.io包下,但是筆者仍然把它們劃分在一起,因爲筆者認爲I/O的核心問題要麼是數據格式影響I/O操作,要麼是傳輸方式影響I/O操作,也就是將什麼樣的數據寫到什麼地方的問題。

I/O只是人與機器或者機器與機器交互的手段,除了它們能夠完成這個交互功能外,我們關注的就是如何提高它的運行效率了,而數據格式和傳輸方式是影響效率最關鍵的因素。


磁盤I/O工作機制

1.幾種訪問文件的方式

讀取和寫入文件I/O操作都調用操作系統提供的接口,因爲磁盤設備是由操作系統管理的,應用程序要訪問物理設備只能通過系統調用的方式來工作。讀和寫分別對應read()和write()兩個系統調用。而只要是系統調用就可能存在內核空間地址和用戶空間地址切換的問題,這是操作系統爲了保護系統本身的運行安全而將內核程序運行的內存空間和用戶的內存空間隔離造成的。但是這樣雖然保證了內核程序運行的安全性,但是也必然存在數據可能需要從內核空間向用戶空間複製的問題。

如果遇到非常耗時的操作,如磁盤I/O,數據從磁盤複製到內核空間,然後又從內核空間複製到用戶空間,將會非常緩慢。這時操作系統爲了加速I/O訪問,在內核空間使用緩存機制。

標準訪問文件方式:

當應用程序調用read()接口時,操作系統檢查內核的高速緩存中有沒有需要的數據,如果已經緩存了,那麼就直接從緩存中返回,如果沒有,從磁盤中讀取,然後緩存在操作系統中

直接I/O方式:

應用程序直接訪問磁盤數據,而不經過操作系統內核數據緩衝區

同步訪問文件方式:

數據的讀取和寫入都是同步操作的,它與標準訪問文件方式不同的是,只有當數據被成功寫到磁盤時才返回給應用程序成功標誌。

異步訪問文件方式:

當訪問數據的線程發出請求之後,線程會接着去處理其他事情,而不是阻塞等待,當請求的數據返回後繼續處理下面的操作。

內存映射方式:

操作系統將內存中的某一塊區域與磁盤中的文件關聯起來,當要訪問內存中一段數據時,轉換爲訪問文件的某一段數據。


2.Java訪問磁盤文件

數據在磁盤中的唯一最小描述就是文件,也就是說上層應用程序只能通過文件來操作磁盤上的數據,文件也是操作系統和磁盤驅動器交互的最小單元

java中通常的File並不代表一個真實存在的文件對象,當你指定一個路徑描述符時,它就會返回一個代表這個路徑的一個虛擬對象,這個可能是一個真實存在的文件或者是一個包含多個文件的目錄。在真正讀取這個文件時會檢查這個文件在不在。


3.Java序列化技術

將一個對象轉化成一串二進制表示的字節數組,通過保存或轉移這些字節數據來達到持久化的目的。需要序列化,對象必須繼承java.io.Serializable接口。反序列化則是相反的過程,將這個字節數組再重新構造成對象。

反序列化時,必須有原始類作爲模板,才能將這個對象還原。序列化的數據並不能像class文件那樣保存類的完整的結構信息。

序列化的文件二進制字節數據:

第一部分:序列化文件頭,第二部分:要序列化的類的描述,第三部分:對象中各個屬性項的描述,第四部分:輸出該對象的父類信息描述,第五部分:輸出對象的屬性項的實際值,如果屬性項是一個對象,那麼這裏還將序列話這個對象,規則和第二部分一樣。

複雜對象情況總結:

當父類繼承Serializable接口,所有子類都可以被序列化。

子類實現了Serializable接口,父類沒有,父類中的屬性不能序列化(不報錯,數據會丟失),但是子類中屬性仍能正確序列化。

如果序列化的屬性是對象,這個對象也必須實現Serializable接口,否則會報錯。

在反序列化時,如果對象的屬性有修改或刪減,修改的部分屬性會丟失,但不會報錯。

在反序列化時,如果serialVersionUID被修改,那麼反序列化時會失敗。


網絡I/O工作機制

數據從一臺主機發送到網絡中的另一臺主機需要經過很多步驟。首先需要有相互溝通的意向。其次要有能夠溝通的物理渠道(物理鏈路),雙方見面語言要能夠交流,而且雙方說話的步調要一致,明白什麼時候該自己說話,什麼時候該對方說話(通信協議)

影響網絡傳輸的因素:

將一份數據從一個地方正確的傳輸到另一個地方所需要的時間我們稱爲響應時間,影響這個響應時間的因素有很多:

網絡帶寬:一秒鐘一條物理鏈路最大能夠傳輸的比特數。

傳輸距離:數據在光纖中要走的距離。

TCP擁塞控制:TCP傳輸是一個“停-等-停-等”協議,傳輸方和接受方的步調要一致,要達到這個步調一致就要通過擁塞控制來調節。


Java Socket的工作機制

Socket這個概念沒有對應到一個具體的實體,它描述計算機之間完成相互通信的一種抽象功能。

主機A的應用程序要能和主機B的應用程序通信,必須通過Socket建立連接,而建立Socket連接必須由底層TCP/IP協議來建立TCP連接。建立TCP連接需要底層IP協議來尋址網絡中的主機,但是一臺主機上可能運行着多個應用程序,如何才能與指定的應用程序通信就要通過TCP或UDP的地址也就是端口號來指定。這樣就可以通過一個Socket實例唯一代表一個主機上的應用程序的通信鏈路了。


建立通信鏈路

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

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

所以ServerSocket所關聯的列表中每個數據結構都代表與一個客戶端建立的TCP連接。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章