Java?NIO與IO的區(qū)別以及比較
前言
傳統(tǒng)的socket IO中,需要為每個(gè)連接創(chuàng)建一個(gè)線(xiàn)程,當(dāng)并發(fā)的連接數(shù)量非常巨大時(shí),線(xiàn)程所占用的棧內(nèi)存和CPU線(xiàn)程切換的開(kāi)銷(xiāo)將非常巨大。使用NIO,不再需要為每個(gè)線(xiàn)程創(chuàng)建單獨(dú)的線(xiàn)程,可以用一個(gè)含有限數(shù)量線(xiàn)程的線(xiàn)程池,甚至一個(gè)線(xiàn)程來(lái)為任意數(shù)量的連接服務(wù)。由于線(xiàn)程數(shù)量小于連接數(shù)量,所以每個(gè)線(xiàn)程進(jìn)行IO操作時(shí)就不能阻塞,如果阻塞的話(huà),有些連接就得不到處理,NIO提供了這種非阻塞的能力。
小量的線(xiàn)程如何同時(shí)為大量連接服務(wù)呢,答案就是就緒選擇。這就好比到餐廳吃飯,每來(lái)一桌客人,都有一個(gè)服務(wù)員專(zhuān)門(mén)為你服務(wù),從你到餐廳到結(jié)帳走人,這樣方式的好處是服務(wù)質(zhì)量好,一對(duì)一的服務(wù),VIP啊,可是缺點(diǎn)也很明顯,成本高,如果餐廳生意好,同時(shí)來(lái)100桌客人,就需要100個(gè)服務(wù)員,那老板發(fā)工資的時(shí)候得心痛死了,這就是傳統(tǒng)的一個(gè)連接一個(gè)線(xiàn)程的方式。
老板是什么人啊,精著呢。這老板就得捉摸怎么能用10個(gè)服務(wù)員同時(shí)為100桌客人服務(wù)呢,老板就發(fā)現(xiàn),服務(wù)員在為客人服務(wù)的過(guò)程中并不是一直都忙著,客人點(diǎn)完菜,上完菜,吃著的這段時(shí)間,服務(wù)員就閑下來(lái)了,可是這個(gè)服務(wù)員還是被這桌客人占用著,不能為別的客人服務(wù),用華為領(lǐng)導(dǎo)的話(huà)說(shuō),就是工作不飽滿(mǎn)。那怎么把這段閑著的時(shí)間利用起來(lái)呢。這餐廳老板就想了一個(gè)辦法,讓一個(gè)服務(wù)員(前臺(tái))專(zhuān)門(mén)負(fù)責(zé)收集客人的需求,登記下來(lái),比如有客人進(jìn)來(lái)了、客人點(diǎn)菜了,客人要結(jié)帳了,都先記錄下來(lái)按順序排好。每個(gè)服務(wù)員到這里領(lǐng)一個(gè)需求,比如點(diǎn)菜,就拿著菜單幫客人點(diǎn)菜去了。點(diǎn)好菜以后,服務(wù)員馬上回來(lái),領(lǐng)取下一個(gè)需求,繼續(xù)為別人客人服務(wù)去了。這種方式服務(wù)質(zhì)量就不如一對(duì)一的服務(wù)了,當(dāng)客人數(shù)據(jù)很多的時(shí)候可能需要等待。但好處也很明顯,由于在客人正吃飯著的時(shí)候服務(wù)員不用閑著了,服務(wù)員這個(gè)時(shí)間內(nèi)可以為其他客人服務(wù)了,原來(lái)10個(gè)服務(wù)員最多同時(shí)為10桌客人服務(wù),現(xiàn)在可能為50桌,60客人服務(wù)了。
這種服務(wù)方式跟傳統(tǒng)的區(qū)別有兩個(gè):
- 1、增加了一個(gè)角色,要有一個(gè)專(zhuān)門(mén)負(fù)責(zé)收集客人需求的人。NIO里對(duì)應(yīng)的就是Selector。
- 2、由阻塞服務(wù)方式改為非阻塞服務(wù)了,客人吃著的時(shí)候服務(wù)員不用一直侯在客人旁邊了。傳統(tǒng)的IO操作,比如read(),當(dāng)沒(méi)有數(shù)據(jù)可讀的時(shí)候,線(xiàn)程一直阻塞被占用,直到數(shù)據(jù)到來(lái)。NIO中沒(méi)有數(shù)據(jù)可讀時(shí),read()會(huì)立即返回0,線(xiàn)程不會(huì)阻塞。
NIO中,客戶(hù)端創(chuàng)建一個(gè)連接后,先要將連接注冊(cè)到Selector,相當(dāng)于客人進(jìn)入餐廳后,告訴前臺(tái)你要用餐,前臺(tái)會(huì)告訴你你的桌號(hào)是幾號(hào),然后你就可能到那張桌子坐下了,SelectionKey就是桌號(hào)。當(dāng)某一桌需要服務(wù)時(shí),前臺(tái)就記錄哪一桌需要什么服務(wù),比如1號(hào)桌要點(diǎn)菜,2號(hào)桌要結(jié)帳,服務(wù)員從前臺(tái)取一條記錄,根據(jù)記錄提供服務(wù),完了再來(lái)取下一條。這樣服務(wù)的時(shí)間就被最有效的利用起來(lái)了。
導(dǎo)讀:
J2SE1.4以上版本中發(fā)布了全新的I/O類(lèi)庫(kù)。
NIO庫(kù)提供的一些新特性:非阻塞I/O,字符轉(zhuǎn)換,緩沖以及通道。NIO和IO都在rt.jar包中。
一、NIO的簡(jiǎn)介
NIO包(java.nio.*)引入了四個(gè)關(guān)鍵的抽象數(shù)據(jù)類(lèi)型,它們共同解決傳統(tǒng)的I/O類(lèi)中的一些問(wèn)題。
1. Buffer:它是包含數(shù)據(jù)且用于讀寫(xiě)的線(xiàn)形表結(jié)構(gòu)。其中還提供了一個(gè)特殊類(lèi)用于內(nèi)存映射文件的I/O操作。
2. Charset:它提供Unicode字符串影射到字節(jié)序列以及逆影射的操作。
3. Channels:包含socket,file和pipe三種管道,它實(shí)際上是雙向交流的通道。
4. Selector:它將多元異步I/O操作集中到一個(gè)或多個(gè)線(xiàn)程中(它可以被看成是Unix中select()函數(shù)或Win32中WaitForSingleEvent()函數(shù)的面向?qū)ο蟀姹荆?/p>
二、IO的傳統(tǒng)方式
以網(wǎng)絡(luò)應(yīng)用為例,傳統(tǒng)方式需要監(jiān)聽(tīng)一個(gè)ServerSocket,接受請(qǐng)求的連接為其提供服務(wù)(服務(wù)通常包括了處理請(qǐng)求并發(fā)送響應(yīng))圖一是服務(wù)器的生命周期圖,其中標(biāo)有粗黑線(xiàn)條的部分表明會(huì)發(fā)生I/O阻塞。
圖一
可以分析創(chuàng)建服務(wù)器的每個(gè)具體步驟。首先創(chuàng)建ServerSocket
ServerSocket server=new ServerSocket(10000);
然后接受新的連接請(qǐng)求
Socket newCnotallow=server.accept();
對(duì)于accept方法的調(diào)用將造成阻塞,直到ServerSocket接受到一個(gè)連接請(qǐng)求為止。一旦連接請(qǐng)求被接受,服務(wù)器可以讀客戶(hù)socket中的請(qǐng)求。
InputStream in = newConnection.getInputStream(); InputStreamReader reader = new InputStreamReader(in); BufferedReader buffer = new BufferedReader(reader); Request request = new Request(); while(!request.isComplete()) { String line = buffer.readLine(); request.addLine(line); }
這樣的操作有兩個(gè)問(wèn)題,首先BufferedReader類(lèi)的readLine()方法在其緩沖區(qū)未滿(mǎn)時(shí)會(huì)造成線(xiàn)程阻塞,只有一定數(shù)據(jù)填滿(mǎn)了緩沖區(qū)或者客戶(hù)關(guān)閉了套接字,方法才會(huì)返回。其次,它回產(chǎn)生大量的垃圾,BufferedReader創(chuàng)建了緩沖區(qū)來(lái)從客戶(hù)套接字讀入數(shù)據(jù),但是同樣創(chuàng)建了一些字符串存儲(chǔ)這些數(shù)據(jù)。雖然BufferedReader內(nèi)部提供了StringBuffer處理這一問(wèn)題,但是所有的String很快變成了垃圾需要回收。
同樣的問(wèn)題在發(fā)送響應(yīng)代碼中也存在:
Response response = request.generateResponse(); OutputStream out = newConnection.getOutputStream(); InputStream in = response.getInputStream(); int ch; while(-1 != (ch = in.read())) { out.write(ch); } newConnection.close();
類(lèi)似的,讀寫(xiě)操作被阻塞而且向流中一次寫(xiě)入一個(gè)字符會(huì)造成效率低下,所以應(yīng)該使用緩沖區(qū),但是一旦使用緩沖,流又會(huì)產(chǎn)生更多的垃圾。
傳統(tǒng)的解決方法:
通常在Java中處理阻塞I/O要用到線(xiàn)程(大量的線(xiàn)程)。一般是實(shí)現(xiàn)一個(gè)線(xiàn)程池用來(lái)處理請(qǐng)求,如圖二
圖二
線(xiàn)程使得服務(wù)器可以處理多個(gè)連接,但是它們也同樣引發(fā)了許多問(wèn)題。每個(gè)線(xiàn)程擁有自己的??臻g并且占用一些CPU時(shí)間,耗費(fèi)很大,而且很多時(shí)間是浪費(fèi)在阻塞的I/O操作上,沒(méi)有有效的利用CPU。
三、NIO的詳細(xì)介紹
1.**** Buffer傳統(tǒng)的I/O不斷的浪費(fèi)對(duì)象資源(通常是String)。新I/O通過(guò)使用Buffer讀寫(xiě)數(shù)據(jù)避免了資源浪費(fèi)。Buffer對(duì)象是線(xiàn)性的,有序的數(shù)據(jù)集合,它根據(jù)其類(lèi)別只包含唯一的數(shù)據(jù)類(lèi)型。
java.nio.Buffer 類(lèi)描述
java.nio.ByteBuffer 包含字節(jié)類(lèi)型。 可以從ReadableByteChannel中讀在 WritableByteChannel中寫(xiě)
java.nio.MappedByteBuffer 包含字節(jié)類(lèi)型,直接在內(nèi)存某一區(qū)域映射
java.nio.CharBuffer 包含字符類(lèi)型,不能寫(xiě)入通道
java.nio.DoubleBuffer 包含double類(lèi)型,不能寫(xiě)入通道
java.nio.FloatBuffer 包含float類(lèi)型
java.nio.IntBuffer 包含int類(lèi)型
java.nio.LongBuffer 包含long類(lèi)型
java.nio.ShortBuffer 包含short類(lèi)型
可以通過(guò)調(diào)用allocate(int capacity)方法或者allocateDirect(int capacity)方法分配一個(gè)Buffer。特別的,你可以創(chuàng)建MappedBytesBuffer通過(guò)調(diào)用FileChannel.map(int mode,long position,int size)。直接(direct)buffer在內(nèi)存中分配一段連續(xù)的塊并使用本地訪(fǎng)問(wèn)方法讀寫(xiě)數(shù)據(jù)。非直接(nondirect)buffer通過(guò)使用Java中的數(shù)組訪(fǎng)問(wèn)代碼讀寫(xiě)數(shù)據(jù)。有時(shí)候必須使用非直接緩沖例如使用任何的wrap方法(如ByteBuffer.wrap(byte[]))在Java數(shù)組基礎(chǔ)上創(chuàng)建buffer。
2. 字符編碼向ByteBuffer中存放數(shù)據(jù)涉及到兩個(gè)問(wèn)題:字節(jié)的順序和字符轉(zhuǎn)換。ByteBuffer內(nèi)部通過(guò)ByteOrder類(lèi)處理了字節(jié)順序問(wèn)題,但是并沒(méi)有處理字符轉(zhuǎn)換。事實(shí)上,ByteBuffer沒(méi)有提供方法讀寫(xiě)String。
Java.nio.charset.Charset處理了字符轉(zhuǎn)換問(wèn)題。它通過(guò)構(gòu)造CharsetEncoder和CharsetDecoder將字符序列轉(zhuǎn)換成字節(jié)和逆轉(zhuǎn)換。
3. 通道****(Channel)你可能注意到現(xiàn)有的java.io類(lèi)中沒(méi)有一個(gè)能夠讀寫(xiě)B(tài)uffer類(lèi)型,所以NIO中提供了Channel類(lèi)來(lái)讀寫(xiě)B(tài)uffer。通道可以認(rèn)為是一種連接,可以是到特定設(shè)備,程序或者是網(wǎng)絡(luò)的連接。通道的類(lèi)等級(jí)結(jié)構(gòu)圖如下
圖三
圖中ReadableByteChannel和WritableByteChannel分別用于讀寫(xiě)。
GatheringByteChannel可以從使用一次將多個(gè)Buffer中的數(shù)據(jù)寫(xiě)入通道,相反的,ScatteringByteChannel則可以一次將數(shù)據(jù)從通道讀入多個(gè)Buffer中。你還可以設(shè)置通道使其為阻塞或非阻塞I/O操作服務(wù)。
為了使通道能夠同傳統(tǒng)I/O類(lèi)相容,Channel類(lèi)提供了靜態(tài)方法創(chuàng)建Stream或Reader
4.**** Selector在過(guò)去的阻塞I/O中,我們一般知道什么時(shí)候可以向stream中讀或?qū)?,因?yàn)榉椒ㄕ{(diào)用直到stream準(zhǔn)備好時(shí)返回。但是使用非阻塞通道,我們需要一些方法來(lái)知道什么時(shí)候通道準(zhǔn)備好了。在NIO包中,設(shè)計(jì)Selector就是為了這個(gè)目的。SelectableChannel可以注冊(cè)特定的事件,而不是在事件發(fā)生時(shí)通知應(yīng)用,通道跟蹤事件。然后,當(dāng)應(yīng)用調(diào)用Selector上的任意一個(gè)selection方法時(shí),它查看注冊(cè)了的通道看是否有任何感興趣的事件發(fā)生。
圖四是selector和兩個(gè)已注冊(cè)的通道的例子:
圖四
并不是所有的通道都支持所有的操作。SelectionKey類(lèi)定義了所有可能的操作位,將要用兩次。首先,當(dāng)應(yīng)用調(diào)用SelectableChannel.register(Selector sel,int op)方法注冊(cè)通道時(shí),它將所需操作作為第二個(gè)參數(shù)傳遞到方法中。然后,一旦SelectionKey被選中了,SelectionKey的readyOps()方法返回所有通道支持操作的數(shù)位的和。SelectableChannel的validOps方法返回每個(gè)通道允許的操作。注冊(cè)通道不支持的操作將引發(fā)IllegalArgumentException異常。下表列出了SelectableChannel子類(lèi)所支持的操作。
ServerSocketChannel OP_ACCEPT SocketChannel OP_CONNECT, OP_READ, OP_WRITE DatagramChannel OP_READ, OP_WRITE Pipe.SourceChannel OP_READ Pipe.SinkChannel OP_WRITE
四. 舉例說(shuō)明
1. 簡(jiǎn)單網(wǎng)頁(yè)內(nèi)容下載這個(gè)例子非常簡(jiǎn)單,類(lèi)SocketChannelReader使用SocketChannel來(lái)下載特定網(wǎng)頁(yè)的HTML內(nèi)容。
package examples.nio;
package com.yineng.mycat; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; import java.net.InetSocketAddress; import java.io.IOException; /** * @author kzfy * @data 2015/12/21 */ public class SocketChannelReader{ private Charset charset=Charset.forName("UTF-8");//創(chuàng)建UTF-8字符集 private SocketChannel channel; public void getHTMLContent(){ try{ connect(); sendRequest(); readResponse(); }catch(IOException e){ System.err.println(e.toString()); }finally{ if(channel!=null){ try{ channel.close(); }catch(IOException e){} } } } private void connect()throws IOException{//連接到CSDN InetSocketAddress socketAddress= new InetSocketAddress("http://www.csdn.net",80); channel=SocketChannel.open(socketAddress); //使用工廠方法open創(chuàng)建一個(gè)channel并將它連接到指定地址上 //相當(dāng)與SocketChannel.open().connect(socketAddress);調(diào)用 } private void sendRequest()throws IOException{ channel.write(charset.encode("GET /document\r\n\r\n"));//發(fā)送GET請(qǐng)求到CSDN的文檔中心 //使用channel.write方法,它需要CharByte類(lèi)型的參數(shù),使用 //Charset.encode(String)方法轉(zhuǎn)換字符串。 } private void readResponse()throws IOException{//讀取應(yīng)答 ByteBuffer buffer=ByteBuffer.allocate(1024);//創(chuàng)建1024字節(jié)的緩沖 while(channel.read(buffer)!=-1){ buffer.flip();//flip方法在讀緩沖區(qū)字節(jié)操作之前調(diào)用。 System.out.println(charset.decode(buffer)); //使用Charset.decode方法將字節(jié)轉(zhuǎn)換為字符串 buffer.clear();//清空緩沖 } } public static void main(String [] args){ new SocketChannelReader().getHTMLContent(); } }
到此這篇關(guān)于Java NIO與IO的區(qū)別以及比較的文章就介紹到這了,更多相關(guān)Java NIO與IO 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springMVC+jersey實(shí)現(xiàn)跨服務(wù)器文件上傳
這篇文章主要介紹了springMVC+jersey實(shí)現(xiàn)跨服務(wù)器文件上傳,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08SpringBoot實(shí)現(xiàn)excel生成并且通過(guò)郵件發(fā)送的步驟詳解
實(shí)際開(kāi)發(fā)中,特別是在B端產(chǎn)品的開(kāi)發(fā)中,我們經(jīng)常會(huì)遇到導(dǎo)出excel的功能,更進(jìn)階一點(diǎn)的需要我們定期生成統(tǒng)計(jì)報(bào)表,然后通過(guò)郵箱發(fā)送給指定的人員,?今天要帶大家來(lái)實(shí)現(xiàn)的就是excel生成并通過(guò)郵件發(fā)送,需要的朋友可以參考下2023-10-10關(guān)于Integer.parseInt()方法的使用
這篇文章主要介紹了關(guān)于Integer.parseInt()方法的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11Springboot實(shí)現(xiàn)緩存預(yù)熱的方法
在系統(tǒng)啟動(dòng)之前通過(guò)預(yù)先將常用數(shù)據(jù)加載到緩存中,以提高緩存命中率和系統(tǒng)性能的過(guò)程,緩存預(yù)熱的目的是盡可能地避免緩存擊穿和緩存雪崩,這篇文章主要介紹了Springboot實(shí)現(xiàn)緩存預(yù)熱,需要的朋友可以參考下2024-03-03GsonFormat快速生成JSon實(shí)體類(lèi)的實(shí)現(xiàn)
GsonFormat主要用于使用Gson庫(kù)將JSONObject格式的String?解析成實(shí)體,本文主要介紹了GsonFormat快速生成JSon實(shí)體類(lèi)的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-05-05VsCode搭建Spring Boot項(xiàng)目并進(jìn)行創(chuàng)建、運(yùn)行、調(diào)試
這篇文章主要介紹了VsCode搭建Spring Boot項(xiàng)目并進(jìn)行創(chuàng)建、運(yùn)行、調(diào)試 ,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05Spring的@RequestParam對(duì)象綁定方式
這篇文章主要介紹了Spring的@RequestParam對(duì)象綁定方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10MyBatis中example.createCriteria()方法的具體使用
本文詳細(xì)介紹了MyBatis的Example工具的使用方法,包括鏈?zhǔn)秸{(diào)用指定字段、設(shè)置查詢(xún)條件、支持多種查詢(xún)方式等,還介紹了mapper的crud方法、and/or方法的使用,以及如何進(jìn)行多條件和多重條件查詢(xún),感興趣的可以了解一下2024-10-10