基础篇--Java IO--概览
字符流、字節流、輸入流、輸出流
Java 中使用IO(輸入輸出)來讀取和寫入,讀寫磁盤文件、內存、網絡數據。輸入輸出是相對內存而言,往內存中讀數據就為輸入流,從內存中往外寫就是輸出流。
根據處理類型分為字符流、字節流。
-
字節流處理所有類型數據,以Stream結尾;
-
字符流處理文本數據,以Reader、Writer結尾;
Java IO類見下圖:
同步、異步、阻塞、非阻塞
synchronous、asynchronous、blocking、non-blocking同步、異步關注的是消息通信機制
同步,是在發起調用后,在得到結果前,該調用不會返回。等到調用返回后,就能拿到返回值。調用者主動等待調用結果。
異步, 是在發起調用后,調用直接返回,所以無返回結果。后續通過狀態、回調通知調用者。阻塞、非阻塞關注的是程序在等待調用結果(返回值、消息)時的狀態
阻塞調用,是指在調用結果返回前,當前線程會被掛起。調用線程只有在得到結果之后才會返回。
非阻塞調用,是指在調用結果返回前,該調用不會阻塞當前線程。換句話說:
同步、異步區別:是否需要等待調用結果,才能進行下一步。
阻塞、非阻塞:進程\線程要訪問的數據就緒前,進程\線程是否需要等待。
Linux的5種IO模型
1.阻塞IO模型
Linux中,默認情況下socket是阻塞的,一個典型的讀操作流程如下:
當用戶進程調用了recvfrom這個系統調用,kernel就開始了I/O的第一個階段:準備數據。對于network io來說,很多時候數據在一開始還沒有到達(比如,還沒有收到一個完整的UDP包),這個時候kernel就要等待足夠的數據到來。而在用戶進程這邊,整個進程會被阻塞。
當kernel一直等到數據準備好了,它就會將數據從kernel系統緩沖區中拷貝到用戶內存,然后kernel返回結果,用戶進程才解除block的狀態,重新運行起來。所以,blocking IO的特點就是在I/O執行的兩個階段都是block。
Windows Socket API
socket()函數和WSASocket()函數創建套接字時,默認的套接字是阻塞的。線程會阻塞等待,直到Windows Sockets API執行完成。
bind()、listen()函數,不會阻塞線程
可能會阻塞套接字的Windows Socket API調用分為4類
輸入操作:recv()、recvfrom()、WSARecv()和WSARecvfrom()函數。以阻塞套接字為參數的該函數接受數據。如果此時套接字緩沖區中無數據,則調用線程一直睡眠到數據到來
輸出操作:send()、sendto()、WSASend()和WSASendto()函數。調用該函數發送數據。如果套接字緩沖區無可用空間,則調用線程會一直睡眠到有可用空間。
接受連接:accept()和WSAAcept()函數。以阻塞套接字為參數的該函數等待接受對方的連接請求。無則線程休眠。
外出連接:connect()和WSAConnect()函數。對于TCP連接,客戶端以阻塞套接字為參數,調用該函數向服務器發起連接。
該函數在接收到服務器的應答前,不會返回。 這意味著TCP連接總會等待至少到服務器的一次往返時間。
阻塞模式的套接字,開發實現簡單。但是并發能力較弱,擴展性弱。
2.非阻塞IO模型
Linux下,可以設置socket為non-blocking。這種情況下的讀取流程如下:
用戶進程調用recvfrom()時,如果kernal數據還未準備好,則直接返回error。用戶線程不斷重試,直到kernal準備好后,就馬上將數據拷貝到用戶內存,然后返回。所以這種模式下,用戶線程需要不斷主動詢問kernal數據好了沒。
3.IO復用模型(IO multiplexing)
IO復用模型又稱event driven I/O,是在實際中使用最多的I/O模型。基本原理是select/epoll這個方法不斷輪訓所負責的socket,當某個socket有數據到達時,就通知用戶線程。流程如下:
當用戶線程調用select(),整個線程就會block,同時kernal會監聽所有select負責的socket,一旦有socket的數據準備好了,select()就會返回。這時候用戶線程再去調用read()操作,將數據從kernal拷貝到用戶線程。
當線程數量較少時,這種方式可能比阻塞I/O模型效率更低,因為多了select()操作。select/epoll的優勢是能處理更多的連接,在高并發場景效率更高。
4.信號驅動I/O模型(Signal-driven I/O)
首先我們需要允許套接字使用信號驅動IO,并安裝一個信號處理函數,然后進程繼續運行并不阻塞。當數據準備好時,進程會收到一個SIGIO信號,在信號處理函數中調用IO操作函數處理數據。流程如下:(這種模型在實際中并不常用)
5.異步I/O模型(Asynchronous I/O)
這種模型不常用,流程如下:
用戶線程發起read操作后,立刻就可以去做別的事情。從kernal角度,當它收到一個asynchroous read請求之后,會立刻返回,不會阻塞用戶進程。然后kernal等待數據準備完成,將數據拷貝到用戶內存,之后給用戶進程發送一個signal,告訴它read操作完成了。
總結
1:blocking和non-blocking的區別
blocking IO會阻塞住對應的進程直到操作完成,non-blocking IO在kernal還未準備好數據的時候,直接返回。
1:synchronous I/O 和asynchronous IO的區別
區別在于synchronous I/O做I/O操作的時候會將進程阻塞。所以,blocking I/O,non-blocking I/O,I/O multiplexing,Signal-driven I/O都屬于synchronous I/O,只有Asynchronous I/O屬于asynchronous IO
擴展:select、poll、epoll簡介
epoll跟select都能提供多路I/O復用的解決方案。在現在的Linux內核里有都能夠支持,其中epoll是Linux所特有,而select則應該是POSIX所規定,一般操作系統均有實現。
1.select
select本質上是通過設置或者檢查存放fd標志位的數據結構來進行下一步處理。這樣所帶來的缺點是:
單個進程可監視的fd數量被限制,即能監聽端口的大小有限:
一般來說這個數目和系統內存關系很大,具體數目可以cat /proc/sys/fs/file-max察看。32位機默認是1024個。64位機默認是2048
對socket進行掃描時是線性掃描,即采用輪詢的方法,效率較低:
當套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。如果能給套接字注冊某個回調函數,當他們活躍時,自動完成相關操作,那就避免了輪詢,這正是epoll與kqueue做的。
需要維護一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構時復制開銷大
2.poll
poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,然后查詢每個fd對應的設備狀態,如果設備就緒則在設備等待隊列中加入一項并繼續遍歷,如果遍歷完所有fd后沒有發現就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒后它又要再次遍歷fd。這個過程經歷了多次無謂的遍歷。
它沒有最大連接數的限制,原因是它是基于鏈表來存儲的,但是同樣有一個缺點:
3.epoll
epoll支持水平觸發和邊緣觸發,最大的特點在于邊緣觸發,它只告訴進程哪些fd剛剛變為就需態,并且只會通知一次。還有一個特點是,epoll使用“事件”的就緒通知方式,通過epoll_ctl注冊fd,一旦該fd就緒,內核就會采用類似callback的回調機制來激活該fd,epoll_wait便可以收到通知。
epoll的優點:
沒有最大并發連接的限制,能打開的FD的上限遠大于1024(1G的內存上能監聽約10萬個端口)
效率提升,不是輪詢的方式,不會隨著FD數目的增加效率下降。只有活躍可用的FD才會調用callback函數
即Epoll最大的優點就在于它只管你“活躍”的連接,而跟連接總數無關,因此在實際的網絡環境中,Epoll的效率就會遠遠高于select和poll
內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減少復制開銷
select、poll、epoll 區別
| poll | 同上 |
| epoll | epoll通過內核和用戶空間共享一塊內存來實現 |
4、Linux I/O模型總結:
綜上,在選擇select,poll,epoll時要根據具體的使用場合以及這三種方式的自身特點:
1、表面上看epoll的性能最好,但是在連接數少并且連接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機制需要很多函數回調。
2、select低效是因為每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設計改善
總結
以上是生活随笔為你收集整理的基础篇--Java IO--概览的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 新注册下来的公司如何开票新注册下来的公司
- 下一篇: Java ASM与Javassit