您好,登錄后才能下訂單哦!
這篇文章主要介紹“Java中的NIO是什么”,在日常操作中,相信很多人在Java中的NIO是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java中的NIO是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
??NIO即NEW I/O,是在JDK1.4引入的一套新的I/O標準。NIO是基于Block,以塊為基本單元處理數(shù)據(jù)。并且為所有的基礎類型提供了Buffer支持。 ?? 數(shù)據(jù)從Channel中讀到Buffer中去,或者從Buffer寫入Channel中。Selector監(jiān)聽多個Channel的事件,比如打開鏈接或數(shù)據(jù)到達。
graph LR A((Channel))--讀取-->B[Buffer]
graph LR A[Buffer]--寫入-->B((Channel))
1、傳統(tǒng)的I/O基于字節(jié)流和字符流的操作,NIO則是基于Channel和Buffer進行數(shù)據(jù)操作。 2、IO是面向流的,NIO是面向Buffer緩沖區(qū)的。 面向流意味著讀取數(shù)據(jù)從流中挨個讀取字節(jié),直至讀完全部字節(jié)。如果需要對讀取的數(shù)據(jù)進行前后移動操作,則需要建立緩沖區(qū)。而NIO的Buffer正好做到這一點,數(shù)據(jù)從Channel被讀取到緩沖區(qū)后,可以很方便的的操作數(shù)據(jù)。 3、IO操作是阻塞模式,調(diào)用read()或者write()操作時,線程阻塞,直至數(shù)據(jù)的讀或?qū)懭拷Y束。NIO是非阻塞模式,從Channel讀取數(shù)據(jù)時,如果沒有可用數(shù)據(jù),就什么都不獲取,不會阻塞線程等待,而是把空閑時間用來執(zhí)行其他Channel上的IO操作,所以單線程可以管理多個Channel。
Channel(通道) Buffer(緩沖區(qū)) Selector(選擇區(qū))
簡稱“信道”或“通道”,跟IO流里的Stream一個級別,不過Stream是單向的,Channel是雙向的,所以既可以用來讀操作,也可以用來寫操作。 主要實現(xiàn):
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel
示例: 傳統(tǒng)IO
/** * 傳統(tǒng)IO操作讀取文件 */ public static void oldIo(){ InputStream inputStream = null; try{ inputStream = new BufferedInputStream( new FileInputStream("src\\main\\java\\top\\qrainly\\demo\\nio\\OldIO.txt")); byte[] bytes = new byte[1024]; int read = inputStream.read(bytes); System.out.println(read); while (read!=-1){ for(int i=0;i<read;i++){ System.out.println((char) bytes[i]); } read = inputStream.read(bytes); } }catch (IOException e){ e.printStackTrace(); }finally { try{ if(inputStream != null){ inputStream.close(); } }catch (IOException e){ e.printStackTrace(); } } }
NIO
/** * NIO操作 */ public static void nio(){ RandomAccessFile file = null; try{ file = new RandomAccessFile("src\\main\\java\\top\\qrainly\\demo\\nio\\NIO.txt","rw"); FileChannel fileChannel = file.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int read = fileChannel.read(buffer); System.out.println(read); while (read != -1){ buffer.flip(); while (buffer.hasRemaining()){ System.out.println((char) buffer.get()); } buffer.compact(); read = fileChannel.read(buffer); } }catch (IOException e){ e.printStackTrace(); }finally { try{ if(file != null){ file.close(); } }catch (IOException e){ e.printStackTrace(); } } }
NIO拷貝文件
/** * NIO拷貝文件 * @param source * @param target */ public static void nioCopyFile(String source,String target){ FileInputStream fileInputStream = null; FileOutputStream fileOutputStream = null; FileChannel readChannel = null; FileChannel writeChannel = null; try { fileInputStream = new FileInputStream(source); fileOutputStream = new FileOutputStream(target); readChannel = fileInputStream.getChannel(); writeChannel = fileOutputStream.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); while (true){ buffer.clear(); int read = readChannel.read(buffer); if(read == -1){ //讀取完畢 break; } buffer.flip(); //寫入文件 writeChannel.write(buffer); } } catch (IOException e) { e.printStackTrace(); }finally { try { if(readChannel != null){ readChannel.close(); } if(writeChannel != null){ writeChannel.close(); } } catch (IOException e) { e.printStackTrace(); } } }
套接字的某些操作可能會無限期地阻塞 對accept()方法的調(diào)用可能會因為等待一個客戶端連接而阻塞; 對read()方法的調(diào)用可能會因為沒有數(shù)據(jù)可讀而阻塞,直到連接的另一端傳來新的數(shù)據(jù)。 NIO的channel抽象的一個重要特征就是可以通過配置它的阻塞行為,以實現(xiàn)非阻塞式的信道
channel.configureBlocking(false)
示例:
/** * 客戶端 NIO實現(xiàn) */ public static void client(){ ByteBuffer buffer = ByteBuffer.allocate(1024); SocketChannel socketChannel = null; try{ //打開SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("localhost",8080)); if(socketChannel.finishConnect()){ int i = 0; while (true){ TimeUnit.SECONDS.sleep(1); //讀取數(shù)據(jù) String info = "I'm "+i+++"-th from client"; buffer.clear(); buffer.put(info.getBytes()); buffer.flip(); while (buffer.hasRemaining()){ System.out.println(buffer); socketChannel.write(buffer); } } } }catch (IOException | InterruptedException e){ e.printStackTrace(); } finally { try{ if(socketChannel != null){ //關閉SocketChannel socketChannel.close(); } }catch (IOException e){ e.printStackTrace(); } } }
服務端NIO實現(xiàn)
private static final int BUF_SIZE = 1024; private static final int PORT = 8080; private static final int TIME_OUT = 3000; public static void selector(){ Selector selector = null; ServerSocketChannel serverSocketChannel = null; try{ selector = Selector.open(); serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(PORT)); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true){ if(selector.select(TIME_OUT) == 0){ System.out.println("等待連接..."); continue; } Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()){ SelectionKey key= iterator.next(); if(key.isAcceptable()){ handleAccept(key); } if(key.isReadable()){ handleRead(key); } if(key.isWritable() && key.isValid()){ handleWrite(key); } if(key.isConnectable()){ System.out.println("連接成功!"); } iterator.remove(); } } }catch (IOException e){ e.printStackTrace(); }finally { try{ if(selector != null){ selector.close(); } if(serverSocketChannel != null){ serverSocketChannel.close(); } }catch (IOException e){ e.printStackTrace(); } } } /** * 處理連接 * @param key * @throws IOException */ public static void handleAccept(SelectionKey key) throws IOException { ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); //監(jiān)聽連接 SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(key.selector(),SelectionKey.OP_READ, ByteBuffer.allocateDirect(BUF_SIZE)); } /** * 處理讀 * @param key * @throws IOException */ public static void handleRead(SelectionKey key) throws IOException { SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer buffer = (ByteBuffer) key.attachment(); long bytesRead = socketChannel.read(buffer); while (bytesRead > 0){ buffer.flip(); while (buffer.hasRemaining()){ System.out.println((char) buffer.get()); } buffer.clear(); bytesRead = socketChannel.read(buffer); } if(bytesRead == -1){ socketChannel.close(); } } /** * 處理寫 * @param key * @throws IOException */ public static void handleWrite(SelectionKey key) throws IOException { ByteBuffer buffer = (ByteBuffer) key.attachment(); buffer.flip(); SocketChannel socketChannel = (SocketChannel) key.channel(); while (buffer.hasRemaining()){ socketChannel.write(buffer); } buffer.compact(); }
??Selector類可以用于避免使用阻塞式客戶端中很浪費資源的“忙等”方法需要讀取和分發(fā)。這就需要一種方法阻塞等待,直到至少有一個信道可以進行I/O操作,并指出是哪個信道。NIO的選擇器就實現(xiàn)了這樣的功能。 一個Selector實例可以同時檢查一組信道的I/O狀態(tài)。用專業(yè)術語來說,選擇器就是一個多路開關選擇器,因為一個選擇器能夠管理多個信道上的I/O操作。 然而如果用傳統(tǒng)的方式來處理這么多客戶端,使用的方法是循環(huán)地一個一個地去檢查所有的客戶端是否有I/O操作,如果當前客戶端有I/O操作,則可能把當前客戶端扔給一個線程池去處理,如果沒有I/O操作則進行下一個輪詢,當所有的客戶端都輪詢過了又接著從頭開始輪詢;這種方法是非常笨而且也非常浪費資源,因為大部分客戶端是沒有I/O操作,我們也要去檢查;而Selector就不一樣了,它在內(nèi)部可以同時管理多個I/O,當一個信道有I/O操作的時候,他會通知Selector,Selector就是記住這個信道有I/O操作,并且知道是何種I/O操作,是讀呢?是寫呢? 還是接受新的連接; ??使用Selector,它返回的結果只有兩種結果,一種是0,即在你調(diào)用的時刻沒有任何客戶端需要I/O操作,另一種結果是一組需要I/O操作的客戶端,這時你就根本不需要再檢查了,因為它返回給你的肯定是你想要的。 這樣一種通知的方式比那種主動輪詢的方式要高效得多!
??與Selector一起使用時,Channel必須處于非阻塞模式下。這意味著不能將FileChannel與Selector一起使用, 因為FileChannel不能切換到非阻塞模式。而套接字通道都可以。
??異步網(wǎng)絡IO Selector運行單線程處理多個Channel,如果應用打開了多信道,單個連接流量很低,此時使用Selector就會很方便。向Selector注冊Channel,然后條用select()方法,這個方法會一直阻塞到有注冊的信道有事件就緒,例如連接打開或者數(shù)據(jù)到達。 ??要使用選擇器(Selector),需要創(chuàng)建一個Selector實例(使用靜態(tài)工廠方法open())并將其注冊(register)到想要監(jiān)控的信道上(注意,這要通過channel的方法實現(xiàn),而不是使用selector的方法)。最后,調(diào)用選擇器的select()方法。該方法會阻塞等待,直到有一個或更多的信道準備好了I/O操作或等待超時。select()方法將返回可進行I/O操作的信道數(shù)量?,F(xiàn)在,在一個單獨的線程中, 通過調(diào)用select()方法就能檢查多個信道是否準備好進行I/O操作。如果經(jīng)過一段時間后仍然沒有信道準備好,select()方法就會返回0, 并允許程序繼續(xù)執(zhí)行其他任務。
Buffer中有四個個重要的參數(shù):位置(position)、容量(capactiy)、上限(limit)和標記(mark)
參數(shù) | 寫模式 | 寫模式 |
---|---|---|
位置(position) | 當前緩沖區(qū)的位置,將從position的下一個位置開始寫數(shù)據(jù) | 當前緩沖區(qū)讀取的位置,將從position下一個位置 |
容量(capactiy) | 緩沖區(qū)的總容量上限 | 緩沖區(qū)的總容量上限 |
上限(limit) | 緩沖區(qū)的時機上限,limit<=capactiy | 代表可讀取的總容量,和上次寫入的數(shù)據(jù)量相等 |
標記(mark) | 用于記錄position前一個位置 | 用于記錄position前一個位置 |
分配空間(ByteBuffer buf = ByteBuffer.allocate(1024); 或者allocateDirector)
寫入數(shù)據(jù)到Buffer(int bytesRead = fileChannel.read(buf);)
調(diào)用filp()方法( buf.flip();)
從Buffer中讀取數(shù)據(jù)(System.out.print((char)buf.get());)
調(diào)用clear()方法或者compact()方法 Buffer顧名思義:緩沖區(qū),實際上是一個容器,一個連續(xù)數(shù)組。Channel提供從文件、網(wǎng)絡讀取數(shù)據(jù)的渠道,但是讀寫的數(shù)據(jù)都必須經(jīng)過Buffer。 從Channel寫到Buffer (fileChannel.read(buf))通過Buffer的put()方法 (buf.put(…)) 從Buffer讀取到Channel (channel.write(buf))使用get()方法從Buffer中讀取數(shù)據(jù) (buf.get())
1、ByteBuffer.allocate(10)方法創(chuàng)建了一個10個byte的數(shù)組緩沖區(qū),position的位置為0,capacity和limit默認都是數(shù)組長度 2、當我們寫入5個字節(jié)時,position變?yōu)?,limit和capacity不變 3、將緩沖區(qū)中的5個字節(jié)數(shù)據(jù)寫入Channel的通信信道,調(diào)用ByteBuffer.flip()方法,position變?yōu)?,limit變?yōu)?,capactiy不變 4、下一次寫入數(shù)據(jù)前調(diào)用clear()方法 -- 調(diào)用clear()方法:position將被設回0,limit設置成capacity(Buffer中的數(shù)據(jù)并未被清除) -- 調(diào)用compact()方法。compact()方法將所有未讀的數(shù)據(jù)拷貝到Buffer起始處。然后將position設到最后一個未讀元素正后面。 limit屬性依然像clear()方法一樣,設置成capacity。不會覆蓋未讀的數(shù)據(jù)。 5、調(diào)用Buffer.mark()方法,可以標記Buffer中的一個特定的position,之后可以通過調(diào)用Buffer.reset()方法恢復到這個position。 Buffer.rewind()方法將position設回0,并清除標記位,所以可以重讀Buffer中的所有數(shù)據(jù)。limit保持不變, 仍然表示能從Buffer中讀取多少個元素 代碼示例
/** * Buffer操作過程 參數(shù)變化 */ public static void bufferParams(){ //15個字節(jié)大小的緩沖區(qū) ByteBuffer b=ByteBuffer.allocate(15); System.out.println("########創(chuàng)建15個字節(jié)的數(shù)組緩沖區(qū)########"); System.out.println("limit="+b.limit()+" capacity="+b.capacity()+" position="+b.position()); //存入10個字節(jié)數(shù)據(jù) System.out.println("########開始存入十個字節(jié)數(shù)據(jù)########"); for(int i=0;i<10;i++){ b.put((byte)i); System.out.println("########存入-->"+i+"########"); } System.out.println("########存入十個字節(jié)數(shù)據(jù)完成########"); System.out.println("limit="+b.limit()+" capacity="+b.capacity()+" position="+b.position()); //重置position System.out.println("########調(diào)用flip()方法########"); b.flip(); System.out.println("########調(diào)用完flip()方法后########"); System.out.println("limit="+b.limit()+" capacity="+b.capacity()+" position="+b.position()); System.out.println("########開始讀取五個字節(jié)數(shù)據(jù)########"); for(int i=0;i<5;i++){ byte b1 = b.get(); System.out.print(b1); System.out.println("########讀取字節(jié)數(shù)據(jù)-->"+b1+"########"); } System.out.println("########讀取五個字節(jié)數(shù)據(jù)完成########"); System.out.println(); System.out.println("limit="+b.limit()+" capacity="+b.capacity()+" position="+b.position()); System.out.println("########調(diào)用flip()方法########"); b.flip(); System.out.println("########調(diào)用完flip()方法后########"); System.out.println("limit="+b.limit()+" capacity="+b.capacity()+" position="+b.position()); }
打印結果
########創(chuàng)建15個字節(jié)的數(shù)組緩沖區(qū)######## limit=15 capacity=15 position=0 ########開始存入十個字節(jié)數(shù)據(jù)######## ########存入-->0######## ########存入-->1######## ########存入-->2######## ########存入-->3######## ########存入-->4######## ########存入-->5######## ########存入-->6######## ########存入-->7######## ########存入-->8######## ########存入-->9######## ########存入十個字節(jié)數(shù)據(jù)完成######## limit=15 capacity=15 position=10 ########調(diào)用flip()方法######## ########調(diào)用完flip()方法后######## limit=10 capacity=15 position=0 ########開始讀取五個字節(jié)數(shù)據(jù)######## 0########讀取字節(jié)數(shù)據(jù)-->0######## 1########讀取字節(jié)數(shù)據(jù)-->1######## 2########讀取字節(jié)數(shù)據(jù)-->2######## 3########讀取字節(jié)數(shù)據(jù)-->3######## 4########讀取字節(jié)數(shù)據(jù)-->4######## ########讀取五個字節(jié)數(shù)據(jù)完成######## limit=10 capacity=15 position=5 ########調(diào)用flip()方法######## ########調(diào)用完flip()方法后######## limit=5 capacity=15 position=0
1、rewind() – 將position置零,并清除標志位(mark 2、clear() – 將position置零,同時將limit設置為capacity的大小,并清除了標志mark 3、flip() – 先將limit設置到position所在位置,然后將position置零,并清除標志位mark – 通常在讀寫轉換時使用
示例:
/** * 文件映射到內(nèi)存 */ public static void mapperMemory(){ RandomAccessFile randomAccessFile = null; try{ randomAccessFile = new RandomAccessFile( "src\\main\\java\\top\\qrainly\\demo\\nio\\NIO.txt","rw"); FileChannel fileChannel = randomAccessFile.getChannel(); //將文件映射到內(nèi)存中 MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0,randomAccessFile.length()); while (mappedByteBuffer.hasRemaining()){ System.out.println((char) mappedByteBuffer.get()); } //修改文件 mappedByteBuffer.put(0,(byte)98); }catch (IOException e){ e.printStackTrace(); }finally { try{ if(randomAccessFile != null){ randomAccessFile.close(); } }catch (IOException e){ e.printStackTrace(); } } }
到此,關于“Java中的NIO是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。