java基礎-IO

2018-1-2 by Atlas

1. 分類

  • 分類標準-面向數據類型

字符流:主要操作char類型數據,包含Writer、Reader及其子類。
字節流:主要操作byte類型數據,包含OutputStream、InputStream及其子類。
字節便於計算機讀寫,字符便於人類識別,數據從字節到字符,再從字符到字節,完成人類和計算機之間的交流,也幫助人與人之間的交流。

  • 分類標準-面向數據流向

輸入流:包含InputStream、Reader及其子類
輸出流:包含OutputStream、Writer及其子類。
IO流的一個特點就是對稱性,不論字節、字符,還是輸入、輸出,都是對稱的,數據進進出出完成交流,本文不用代碼舉例描述也非常容易理解和記憶,也是對稱的功勞,實際上接下來java序列化和解序列化將會形象的體會到。
整個IO類族是Writer、Reader及其子類和OutputStream、InputStream及其子類的組合拓展和完善的。

2. 常用類

  • InputStream

InputStream是所有輸入字節流的父類,是一個抽象類。
ByteInputStream、FileInputStream是基本的介質流,分別從Byte數組、本地文件中讀取數據。
ObjectInputStream和FilterInputStream及其子類都是裝飾流。

  • OutputStream

OutputStream是所有輸出字節流的父類,是一個抽象類。
ByteOutputStream、FIleOutputStream是基本的介質流,分別往Byte數組、本地文件中寫入數據。
ObjectOutputStream和FilterOutputStream及其子類都是裝飾流。

  • Reader

Reader是所有字符輸入流的父類,是一個抽象類。
CharArrayReader、StringReader分別從Char數組、String中讀取數據。
PipedReader從與其它線程共享的管道中讀取數據。
BufferedReader、FilterReader及其子類都是裝飾流。

  • Writer

Writer是所有字符輸出流的父類,是一個抽象類。
CharArrayWriter、StringWriter分別往Char數組、String中寫入數據。
PipedWriter往與其他線程共享的管道中寫入數據。
BufferedWriter、FilterWriter及其子類都是裝飾流。

  • InputStreamReader

InputStreamReader是一個連接字節輸入流和字符輸入流的橋樑,FileReader是實現此功能的工具類。

  • OutputStreamWriter

OutputStreamWriter是一個連接字節輸出流和字符輸出流的橋樑,FileWriter是實現此功能的工具類。

3. 理解IO原理

  • 用戶態和內核態

對於操作系統而言,JVM只是一個用戶進程,處於用戶態空間中。而處於用戶態空間的進程是不能直接操作底層的硬件的。而IO操作就需要操作底層的硬件,比如磁盤。因此,IO操作必須得藉助內核的幫助才能完成(中斷,trap),即:會有用戶態到內核態的切換。

  • 用戶緩衝和內核緩衝

優勢:我們寫代碼 new byte[] 數組時,一般是都是“隨意” 創建一個“任意大小”的數組。比如,new byte[128]、new byte[1024]、new byte[4096]....但是,對於磁盤塊的讀取而言,每次訪問磁盤讀數據時,並不是讀任意大小的數據的,而是:每次讀一個磁盤塊或者若干個磁盤塊(這是因爲訪問磁盤操作代價是很大的,而且我們也相信局部性原理) 因此,就需要有一個“中間緩衝區”--即內核緩衝區。先把數據從磁盤讀到內核緩衝區中,然後再把數據從內核緩衝區搬到用戶緩衝區。這也是爲什麼我們總感覺到第一次read操作很慢,而後續的read操作卻很快的原因吧。因爲,對於後續的read操作而言,它所需要讀的數據很可能已經在內核緩衝區了,此時只需將內核緩衝區中的數據拷貝到用戶緩衝區即可,並未涉及到底層的讀取磁盤操作,當然就快了。
劣勢:當需要傳輸的數據遠遠大於內核緩衝區的大小時,內核緩衝區就會成爲瓶頸,原因是它已經起不到“緩衝”的功能。

  • 普通IO處理流程

普通IO處理流程

1)用戶程序創建一個緩衝區。
2)當執行到read()方法時,其實底層是發生了很多操作的:
①內核給磁盤控制器發命令說:我要讀磁盤上的某某塊磁盤塊上的數據。
②在DMA的控制下,把磁盤上的數據讀入到內核緩衝區。
③內核把數據從內核緩衝區複製到用戶緩衝區。

  • NIO處理流程

NIO處理流程

1)內核空間的 buffer 與 用戶空間的 buffer 都映射到同一塊 物理內存區域。
2)對文件的操作不需要再發read 或者 write 系統調用。
3)當用戶進程訪問“內存映射文件”地址時,自動產生缺頁錯誤,然後由底層的OS負責將磁盤上的數據送到內存。
使用內存映射緩衝區來操作文件,它比普通的IO操作讀文件要快得多。甚至比使用文件通道(FileChannel)操作文件 還要快。因爲,使用內存映射緩衝區操作文件時,沒有顯示的系統調用(read,write),而且OS還會自動緩存一些文件頁(memory page)

  • 經典讀文件

大多數WEB應用程序執行的一系列操作:接受用戶請求--->從本地磁盤讀數據--->數據進入內核緩衝區--->用戶緩衝區--->內核緩衝區--->用戶緩衝區--->socket發送數據。

經典讀文件

①第一次上下文切換髮生在 read()方法執行,表示服務器要去磁盤上讀文件了,這會導致一個 sys_read()的系統調用。此時由用戶態切換到內核態,完成的動作是:DMA把磁盤上的數據讀入到內核緩衝區中(這也是第一次拷貝)。
②第二次上下文切換髮生在read()方法的返回(這也說明read()是一個阻塞調用),表示數據已經成功從磁盤上讀到內核緩衝區了。此時,由內核態返回到用戶態,完成的動作是:將內核緩衝區中的數據拷貝到用戶緩衝區(這是第二次拷貝)。
③第三次上下文切換髮生在 send()方法執行,表示服務器準備把數據發送出去了。此時,由用戶態切換到內核態,完成的動作是:將用戶緩衝區中的數據拷貝到內核緩衝區(這是第三次拷貝)。
④第四次上下文切換髮生在 send()方法的返回【這裏的send()方法可以異步返回,所謂異步返回就是:線程執行了send()之後立即從send()返回,剩下的數據拷貝及發送就交給底層操作系統實現了】。此時,由內核態返回到用戶態,完成的動作是:將內核緩衝區中的數據送到 protocol engine.(這是第四次拷貝)。

  • zerocopy transferTo() 讀文件

IO操作需要數據頻繁地在內核緩衝區和用戶緩衝區之間拷貝,而zerocopy技術可以減少這種拷貝的次數,同時也降低了上下文切換(用戶態與內核態之間的切換)的次數。
zerocopy技術的目標就是提高IO密集型JAVA應用程序的性能。

zerocopy transferTo() 讀文件

1)DMA將數據從磁盤讀入 Read buffer中(第一次數據拷貝)。
2)還是在內核空間中,將數據從Read buffer 拷貝到 Socket buffer(第二次數據拷貝)。
3)最終再將數據從 Socket buffer 拷貝到 NIC buffer(第三次數據拷貝)。
4)再從內核態返回到用戶態。
三次數據拷貝和二次上下文切換。

  • 增強的 zerocopy transferTo() 讀文件

如果底層的網絡硬件以及操作系統支持,還可以進一步減少數據拷貝次數 以及 CPU干預次數。

增強的 zerocopy transferTo() 讀文件

1)用戶程序執行 transferTo()方法,導致一次系統調用,從用戶態切換到內核態。
2)DMA將數據從磁盤中拷貝到Read buffer用一個描述符標記此次待傳輸數據的地址以及長度。
3)DMA直接把數據從Read buffer 傳輸到 NIC buffer。

參考文獻:JAVAIO以及NIO理解

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