【学习】009 NIO编程
?
?
NIO概述
什么是NIO?
Java NIO(New IO)是一個可以替代標準Java IO API的IO API(從Java 1.4開始),Java NIO提供了與標準IO不同的IO工作方式。
Java NIO: Channels and Buffers(通道和緩沖區)
標準的IO基于字節流和字符流進行操作的,而NIO是基于通道(Channel)和緩沖區(Buffer)進行操作,數據總是從通道讀取到緩沖區中,或者從緩沖區寫入到通道中。
Java NIO: Non-blocking IO(非阻塞IO)
Java NIO可以讓你非阻塞的使用IO,例如:當線程從通道讀取數據到緩沖區時,線程還是可以進行其他事情。當數據被寫入到緩沖區時,線程可以繼續處理它。從緩沖區寫入通道也類似。
Java NIO: Selectors(選擇器)
Java NIO引入了選擇器的概念,選擇器用于監聽多個通道的事件(比如:連接打開,數據到達)。因此,單個的線程可以監聽多個數據通道。
注意:傳統IT是單向。 NIO類似
區別
| IO | NIO |
| 面向流 | 面向緩沖區 |
| 阻塞IO | 非阻塞IO |
| 無 | 選擇器 |
?
Buffer的數據存取
一個用于特定基本數據類行的容器。有java.nio包定義的,所有緩沖區都是抽象類Buffer的子類。
Java NIO中的Buffer主要用于與NIO通道進行交互,數據是從通道讀入到緩沖區,從緩沖區寫入通道中的。
Buffer就像一個數組,可以保存多個相同類型的數據。根據類型不同(boolean除外),有以下Buffer常用子類:
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
Buffer的概述
1)容量(capacity):表示Buffer最大數據容量,緩沖區容量不能為負,并且建立后不能修改。
2)限制(limit):第一個不應該讀取或者寫入的數據的索引,即位于limit后的數據不可以讀寫。緩沖區的限制不能為負,并且不能大于其容量(capacity)。
3)位置(position):下一個要讀取或寫入的數據的索引。緩沖區的位置不能為負,并且不能大于其限制(limit)。
4)標記(mark)與重置(reset):標記是一個索引,通過Buffer中的mark()方法指定Buffer中一個特定的position,之后可以通過調用reset()方法恢復到這個position。
?
package com.hongmoshui.sum;import java.nio.ByteBuffer;/*** (緩沖區)buffer 用于NIO存儲數據 支持多種不同的數據類型 <br>* 1.byteBuffer <br>* 2.charBuffer <br>* 3.shortBuffer<br>* 4.IntBuffer<br>* 5.LongBuffer<br> * 6.FloatBuffer <br>* 7.DubooBuffer <br>* 上述緩沖區管理的方式 幾乎<br>* 通過allocate() 獲取緩沖區 <br>* 二、緩沖區核心的方法 put 存入數據到緩沖區 get <br> 獲取緩沖區數據 flip 開啟讀模式* 三、緩沖區四個核心屬性<br>* capacity:緩沖區最大容量,一旦聲明不能改變。 limit:界面(緩沖區可以操作的數據大小) limit后面的數據不能讀寫。* position:緩沖區正在操作的位置*/ public class Test004 {public static void main(String[] args) {// 1.指定緩沖區大小1024ByteBuffer buf = ByteBuffer.allocate(1024);System.out.println("--------------------");System.out.println(buf.position());System.out.println(buf.limit());System.out.println(buf.capacity());// 2.向緩沖區存放5個數據buf.put("abcd1".getBytes());System.out.println("--------------------");System.out.println(buf.position());System.out.println(buf.limit());System.out.println(buf.capacity());// 3.開啟讀模式 buf.flip();System.out.println("----------開啟讀模式...----------");System.out.println(buf.position());System.out.println(buf.limit());System.out.println(buf.capacity());byte[] bytes = new byte[buf.limit()];buf.get(bytes);System.out.println(new String(bytes, 0, bytes.length));System.out.println("----------重復讀模式...----------");// 4.開啟重復讀模式 buf.rewind();System.out.println(buf.position());System.out.println(buf.limit());System.out.println(buf.capacity());byte[] bytes2 = new byte[buf.limit()];buf.get(bytes2);System.out.println(new String(bytes2, 0, bytes2.length));// 5.clean 清空緩沖區 數據依然存在,只不過數據被遺忘System.out.println("----------清空緩沖區...----------");buf.clear();System.out.println(buf.position());System.out.println(buf.limit());System.out.println(buf.capacity());System.out.println((char)buf.get());}}make與rest用法
標記(mark)與重置(reset):標記是一個索引,通過Buffer中的mark()方法指定Buffer中一個特定的position,之后可以通過調用reset()方法恢復到這個position。
package com.hongmoshui.sum;import java.nio.ByteBuffer;public class Test002 {public static void main(String[] args) {ByteBuffer buf = ByteBuffer.allocate(1024);String str = "abcd1";buf.put(str.getBytes());// 開啟讀取模式 buf.flip();byte[] dst = new byte[buf.limit()];buf.get(dst, 0, 2);buf.mark();System.out.println(new String(dst, 0, 2));System.out.println(buf.position());buf.get(dst, 2, 2);System.out.println(new String(dst, 2, 2));System.out.println(buf.position());buf.reset();System.out.println("重置恢復到mark位置..");System.out.println(buf.position());}}直接緩沖區與非直接緩沖區別
非直接緩沖區:通過 allocate() 方法分配緩沖區,將緩沖區建立在 JVM 的內存中
直接緩沖區:通過 allocateDirect() 方法分配直接緩沖區,將緩沖區建立在物理內存中。可以提高效率
字節緩沖區要么是直接的,要么是非直接的。如果為直接字節緩沖區,則 Java 虛擬機會盡最大努力直接在此緩沖區上執行本機 I/O 操作。也就是說,在每次調用基礎操作系統的一個本機 I/O 操作之前(或之后),虛擬機都會盡量避免將緩沖區的內容復制到中間緩沖區中(或從中間緩沖區中復制內容)。
直接字節緩沖區可以通過調用此類的 allocateDirect() 工廠方法來創建。此方法返回的緩沖區進行分配和取消分配所需成本通常高于非直接緩沖區。直接緩沖區的內容可以駐留在常規的垃圾回收堆之外,因此,它們對應用程序的內存需求量造成的影響可能并不明顯。所以,建議將直接緩沖區主要分配給那些易受基礎系統的本機 I/O 操作影響的大型、持久的緩沖區。一般情況下,最好僅在直接緩沖區能在程序性能方面帶來明顯好處時分配它們。
直接字節緩沖區還可以通過 FileChannel 的 map() 方法 將文件區域直接映射到內存中來創建。該方法返回MappedByteBuffer 。 Java 平臺的實現有助于通過 JNI 從本機代碼創建直接字節緩沖區。如果以上這些緩沖區中的某個緩沖區實例指的是不可訪問的內存區域,則試圖訪問該區域不會更改該緩沖區的內容,并且將會在訪問期間或稍后的某個時間導致拋出不確定的異常。
字節緩沖區是直接緩沖區還是非直接緩沖區可通過調用其 isDirect() 方法來確定。提供此方法是為了能夠在性能關鍵型代碼中執行顯式緩沖區管理。
package com.hongmoshui.sum;import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.nio.file.Paths; import java.nio.file.StandardOpenOption;public class Test005 {public static void main(String[] args) throws IOException{test1();test2();}// 使用直接緩沖區完成文件的復制(內存映射文件)public static void test2() throws IOException{long start = System.currentTimeMillis();FileChannel inChannel = FileChannel.open(Paths.get("D:/test.txt"), StandardOpenOption.READ);FileChannel outChannel = FileChannel.open(Paths.get("D:/test2.txt"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);// 內存映射文件MappedByteBuffer inMappedByteBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());MappedByteBuffer outMappedByteBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());// 直接對緩沖區進行數據的讀寫操作byte[] dsf = new byte[inMappedByteBuf.limit()];inMappedByteBuf.get(dsf);outMappedByteBuffer.put(dsf);inChannel.close();outChannel.close();long end = System.currentTimeMillis();System.out.println(end - start);}// 1.利用通道完成文件的復制(非直接緩沖區)public static void test1() throws IOException{ // 4400long start = System.currentTimeMillis();FileInputStream fis = new FileInputStream("D:/test.txt");FileOutputStream fos = new FileOutputStream("D:/test2.txt");// ①獲取通道FileChannel inChannel = fis.getChannel();FileChannel outChannel = fos.getChannel();// ②分配指定大小的緩沖區ByteBuffer buf = ByteBuffer.allocate(1024);while (inChannel.read(buf) != -1){buf.flip();// 切換為讀取數據// ③將緩沖區中的數據寫入通道中 outChannel.write(buf);buf.clear();}outChannel.close();inChannel.close();fos.close();fis.close();long end = System.currentTimeMillis();System.out.println(end - start);}}通道(Channel)的原理獲取
通道表示打開到 IO 設備(例如:文件、套接字)的連接。若需要使用 NIO 系統,需要獲取用于連接 IO 設備的通道以及用于容納數據的緩沖區。然后操作緩沖區,對數據進行處理。Channel 負責傳輸, Buffer 負責存儲。通道是由 java.nio.channels 包定義的。 Channel 表示 IO 源與目標打開的連接。Channel 類似于傳統的“流”。只不過 Channel本身不能直接訪問數據, Channel 只能與Buffer 進行交互。
java.nio.channels.Channel 接口:
? ???????? |--FileChannel
? ???????? |--SocketChannel
? ???????? |--ServerSocketChannel
? ???????? |--DatagramChannel
? 獲取通道
? 1. Java 針對支持通道的類提供了 getChannel() 方法
? ???????? 本地 IO:
? ???????? FileInputStream/FileOutputStream
? ???????? RandomAccessFile
? ???????? 網絡IO:
? ???????? Socket
? ???????? ServerSocket
? ??? ????? DatagramSocket??
? 2. 在 JDK 1.7 中的 NIO.2 針對各個通道提供了靜態方法 open()
? 3. 在 JDK 1.7 中的 NIO.2 的 Files 工具類的 newByteChannel()
package com.hongmoshui.sum;import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.nio.file.Paths; import java.nio.file.StandardOpenOption;import org.junit.Test;public class Test006 {@Test// 使用直接緩沖區完成文件的復制(內存映射文件)public void test2() throws IOException{FileChannel inChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ);FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);// 映射文件MappedByteBuffer inMapperBuff = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());MappedByteBuffer outMapperBuff = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());// 直接對緩沖區進行數據讀寫操作byte[] dst = new byte[inMapperBuff.limit()];inMapperBuff.get(dst);outMapperBuff.put(dst);outChannel.close();inChannel.close();}@Test// 1.利用通道完成文件復制(非直接緩沖區)public void test1() throws IOException{FileInputStream fis = new FileInputStream("1.png");FileOutputStream fos = new FileOutputStream("2.png");// ①獲取到通道FileChannel inChannel = fis.getChannel();FileChannel outChannel = fos.getChannel();// ②分配指定大小的緩沖區ByteBuffer buf = ByteBuffer.allocate(1024);while (inChannel.read(buf) != -1){buf.flip();// 切換到讀取模式 outChannel.write(buf);buf.clear();// 清空緩沖區 }// 關閉連接 outChannel.close();inChannel.close();fos.close();fis.close();}}直接緩沖區與非直接緩沖耗時計算
package com.hongmoshui.sum;import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.nio.file.Paths; import java.nio.file.StandardOpenOption;import org.junit.Test;public class Test007 {@Test// 使用直接緩沖區完成文件的復制(內存映射文件) //428、357public void test2() throws IOException{long startTime = System.currentTimeMillis();FileChannel inChannel = FileChannel.open(Paths.get("f://1.mp4"), StandardOpenOption.READ);FileChannel outChannel = FileChannel.open(Paths.get("f://2.mp4"), StandardOpenOption.READ, StandardOpenOption.WRITE,StandardOpenOption.CREATE);// 映射文件MappedByteBuffer inMapperBuff = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());MappedByteBuffer outMapperBuff = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());// 直接對緩沖區進行數據讀寫操作byte[] dst = new byte[inMapperBuff.limit()];inMapperBuff.get(dst);outMapperBuff.put(dst);outChannel.close();inChannel.close();long endTime = System.currentTimeMillis();System.out.println("內存映射文件耗時:" + (endTime - startTime));}@Test// 1.利用通道完成文件復制(非直接緩沖區)public void test1() throws IOException{ // 11953 、3207、3337long startTime = System.currentTimeMillis();FileInputStream fis = new FileInputStream("f://1.mp4");FileOutputStream fos = new FileOutputStream("f://2.mp4");// ①獲取到通道FileChannel inChannel = fis.getChannel();FileChannel outChannel = fos.getChannel();// ②分配指定大小的緩沖區ByteBuffer buf = ByteBuffer.allocate(1024);while (inChannel.read(buf) != -1){buf.flip();// 切換到讀取模式 outChannel.write(buf);buf.clear();// 清空緩沖區 }// 關閉連接 outChannel.close();inChannel.close();fos.close();fis.close();long endTime = System.currentTimeMillis();System.out.println("非緩沖區:" + (endTime - startTime));}}分散讀取與聚集寫入
分散讀取(scattering Reads):將通道中的數據分散到多個緩沖區中
聚集寫入(gathering Writes):將多個緩沖區的數據聚集到通道中
?
package com.hongmoshui.sum;import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel;public class Test008 {public static void main(String[] args) throws IOException{RandomAccessFile raf1 = new RandomAccessFile("test.txt", "rw");// 1.獲取通道FileChannel channel = raf1.getChannel();// 2.分配指定大小的指定緩沖區ByteBuffer buf1 = ByteBuffer.allocate(100);ByteBuffer buf2 = ByteBuffer.allocate(1024);// 3.分散讀取ByteBuffer[] bufs ={ buf1, buf2 };channel.read(bufs);for (ByteBuffer byteBuffer : bufs){// 切換為讀取模式 byteBuffer.flip();}System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));System.out.println("------------------分算讀取線分割--------------------");System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));// 聚集寫入RandomAccessFile raf2 = new RandomAccessFile("test2.txt", "rw");FileChannel channel2 = raf2.getChannel();channel2.write(bufs);}}字符集 Charset
編碼:字符串->字節數組
解碼:字節數組 -> 字符串
package com.hongmoshui.sum;import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder;public class Test009 {public static void main(String[] args) throws CharacterCodingException{// 獲取編碼器【utf-8編碼,中文占3個字節,英文占1個字節】Charset cs1 = Charset.forName("UTF-8");// 獲取編碼器CharsetEncoder ce = cs1.newEncoder();// 獲取解碼器CharsetDecoder cd = cs1.newDecoder();CharBuffer cBuf = CharBuffer.allocate(1024);cBuf.put("洪墨水,哈哈哈!");cBuf.flip();// 編碼ByteBuffer bBuf = ce.encode(cBuf);for (int i = 0; i < 24; i++){System.out.println(bBuf.get());}// 解碼 bBuf.flip();CharBuffer cBuf2 = cd.decode(bBuf);System.out.println(cBuf2.toString());System.out.println("-------------------------------------");Charset cs2 = Charset.forName("UTF-8");bBuf.flip();CharBuffer cbeef = cs2.decode(bBuf);System.out.println(cbeef.toString());}}NIO同步阻塞與同步非阻塞
BIO與NIO
IO(BIO)和NIO區別:其本質就是阻塞和非阻塞的區別
阻塞概念:應用程序在獲取網絡數據的時候,如果網絡傳輸數據很慢,就會一直等待,直到傳輸完畢為止。
非阻塞概念:應用程序直接可以獲取已經準備就緒好的數據,無需等待。
IO為同步阻塞形式,NIO為同步非阻塞形式,NIO并沒有實現異步,在JDK1.7后升級NIO庫包,支持異步非阻塞
同學模型NIO2.0(AIO)
BIO:同步阻塞式IO,服務器實現模式為一個連接一個線程,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,當然可以通過線程池機制改善。?
NIO:同步非阻塞式IO,服務器實現模式為一個請求一個線程,即客戶端發送的連接請求都會注冊到多路復用器上,多路復用器輪詢到連接有I/O請求時才啟動一個線程進行處理。?
AIO(NIO.2):異步非阻塞式IO,服務器實現模式為一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知服務器應用去啟動線程進行處理。?
?
同步時,應用程序會直接參與IO讀寫操作,并且我們的應用程序會直接阻塞到某一個方法上,直到數據準備就緒:
或者采用輪訓的策略實時檢查數據的就緒狀態,如果就緒則獲取數據.
異步時,則所有的IO讀寫操作交給操作系統,與我們的應用程序沒有直接關系,我們程序不需要關系IO讀寫,當操作
系統完成了IO讀寫操作時,會給我們應用程序發送通知,我們的應用程序直接拿走數據極即可。
偽異步
由于BIO一個客戶端需要一個線程去處理,因此我們進行優化,后端使用線程池來處理多個客戶端的請求接入,形成客戶端個數M:線程池最大的線程數N的比例關系,其中M可以遠遠大于N,通過線程池可以靈活的調配線程資源,設置線程的最大值,防止由于海量并發接入導致線程耗盡。
原理:
當有新的客戶端接入時,將客戶端的Socket封裝成一個Task(該Task任務實現了java的Runnable接口)投遞到后端的線程池中進行處理,由于線程池可以設置消息隊列的大小以及線程池的最大值,因此,它的資源占用是可控的,無論多少個客戶端的并發訪問,都不會導致資源的耗盡或宕機。
IO模型關系
什么是阻塞
阻塞概念:應用程序在獲取網絡數據的時候,如果網絡傳輸很慢,那么程序就一直等著,直接到傳輸完畢。
什么是非阻塞
應用程序直接可以獲取已經準備好的數據,無需等待.
IO為同步阻塞形式,NIO為同步非阻塞形式。NIO沒有實現異步,在JDK1.7之后,升級了NIO庫包
,支持異步費阻塞通訊模型NIO2.0(AIO)
NIO非阻塞代碼
/*** 啟動客戶端【nio異步非阻塞】* @param port 端口號* @author 墨水*/private static void upClient(int port) throws IOException{// 1.創建通道SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", port));// 2.切換異步非阻塞sChannel.configureBlocking(false);System.out.println("port:" + port + "客戶端已經啟動....");// 3.指定緩沖區大小ByteBuffer byteBuffer = ByteBuffer.allocate(1024);Scanner scanner = new Scanner(System.in);while (scanner.hasNext()){String str = scanner.next();byteBuffer.put((new Date().toString() + "\n" + str).getBytes());// 4.切換讀取模式 byteBuffer.flip();sChannel.write(byteBuffer);byteBuffer.clear();}sChannel.close();}/*** 啟動服務端【nio異步非阻塞】* @param port 端口號* @author 墨水*/private static void upServer(int port) throws IOException, ClosedChannelException{// 1.創建通道ServerSocketChannel sChannel = ServerSocketChannel.open();// 2.切換讀取模式sChannel.configureBlocking(false);// 3.綁定連接sChannel.bind(new InetSocketAddress(port));// 4.獲取選擇器Selector selector = Selector.open();// 5.將通道注冊到選擇器 "并且指定監聽接受事件" sChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("port:" + port + "服務器端已經啟動....");// 6. 輪訓式 獲取選擇 "已經準備就緒"的事件while (selector.select() > 0){// 7.獲取當前選擇器所有注冊的"選擇鍵(已經就緒的監聽事件)"Iterator<SelectionKey> it = selector.selectedKeys().iterator();while (it.hasNext()){// 8.獲取準備就緒的事件SelectionKey sk = it.next();// 9.判斷具體是什么事件準備就緒if (sk.isAcceptable()){// 10.若"接受就緒",獲取客戶端連接SocketChannel socketChannel = sChannel.accept();// 11.設置阻塞模式socketChannel.configureBlocking(false);// 12.將該通道注冊到服務器上 socketChannel.register(selector, SelectionKey.OP_READ);}else if (sk.isReadable()){// 13.獲取當前選擇器"就緒"// 狀態的通道SocketChannel socketChannel = (SocketChannel) sk.channel();// 14.讀取數據ByteBuffer buf = ByteBuffer.allocate(1024);int len = 0;while ((len = socketChannel.read(buf)) > 0){buf.flip();System.out.println("port:" + port + "||||||||" + new String(buf.array(), 0, len));buf.clear();}}it.remove();}}}選擇KEY
1、SelectionKey.OP_CONNECT
2、SelectionKey.OP_ACCEPT
3、SelectionKey.OP_READ
4、SelectionKey.OP_WRITE
如果你對不止一種事件感興趣,那么可以用“位或”操作符將常量連接起來,如下:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
在SelectionKey類的源碼中我們可以看到如下的4中屬性,四個變量用來表示四種不同類型的事件:可讀、可寫、可連接、可接受連接
?
轉載于:https://www.cnblogs.com/hongmoshui/p/10985902.html
總結
以上是生活随笔為你收集整理的【学习】009 NIO编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java常用工具类---IP工具类、Fi
- 下一篇: 题解 DTOJ #1438. 矮人排队(