更新時間:2022-10-14 來源:黑馬程序員 瀏覽量:
NIO是New I/O的簡稱,與舊式基于流的I/O相對,從名字上來看,它表示新的一套I/O標準。它是從JDK1.4中被納入到JDK中的。
與舊式的IO流相比,NIO是基于Block的,它以塊為單位來處理數據,最為重要的兩個組件是緩沖區Buffer和通道Channel。緩沖區是一塊連續的內存塊,是NIO讀寫數據的載體;通道表示緩沖數據的源頭和目的地,它用于向緩沖區讀取或者寫入數據,是訪問緩沖區的接口。
Buffer的基本原理
Buffer中最重要的3個參數:位置(position)、容量(capacity)、上限(limit)。他們3者的含義如下
位置(position): 表示當前緩沖區的位置,從position位置之后開始讀寫數據。 容量(capacity): 表示緩沖區的最大容量 上限(limit): 表示緩沖區的實際上限,它總是小于或等于容量
緩沖區的容量(capacity)是不變的,而位置(position)和上限(limit)和以根據實際需要而變化。也就是說,可以通過改變當前位置和上限來操作緩沖區內任意位置的數據。
Buffer的常用方法
NIO提供一系列方法來操作Buffer的位置(position)和上限(limit),以及向緩沖區讀寫數據。
put() //向緩沖區position位置添加數據。并且position往后移動,不能超過limit上限。 get() //讀取當前position位置的數據。并且position往后移動,不能超過limit上限。 flip() //將limit置位為當前position位置,再講position設置為0 rewind() //僅將當前position位置設置為0 remaining //獲取緩沖區中當前position位置和limit上限之間的元素數(有效的元素數) hasRemaining() //判斷當前緩沖區是否存在有效的元素數 mark() //在當前position位置打一個標記 reset() //將當前position位置恢復到mark標記的位置。 duplicate() //復制緩沖區
創建緩沖區
//創建一個容量為10的緩沖區 ByteBuffer byteBuffer1=ByteBuffer.allocate(10); //創建一個包裹數據的緩沖區 ByteBuffer byteBuffer2 = ByteBuffer.wrap("abcde".getBytes());
獲取/設置緩沖區參數
//創建一個容量為10的緩沖區 ByteBuffer byteBuffer=ByteBuffer.allocate(10); System.out.println("位置:"+byteBuffer.position()); //0 System.out.println("上限:"+byteBuffer.limit()); //10 System.out.println("容量:"+byteBuffer.capacity());//10
添加數據到緩沖區
//創建一個容量為10的緩沖區 ByteBuffer byteBuffer=ByteBuffer.allocate(10); //添加數據到緩沖區 byteBuffer.put("abcde".getBytes()); System.out.println("position位置:"+byteBuffer.position()); //5 System.out.println("limit上限:"+byteBuffer.limit()); //10 System.out.println("capacity容量:"+byteBuffer.capacity()); //10
rewind重置緩沖區
rewind函數將position置為0位置,并清除標記。
//創建一個容量為10的緩沖區 ByteBuffer byteBuffer=ByteBuffer.allocate(10); //添加數據到緩沖區 byteBuffer.put("abcde".getBytes()); System.out.println("position位置:"+byteBuffer.position()); //5 System.out.println("limit上限:"+byteBuffer.limit()); //10 System.out.println("capacity容量:"+byteBuffer.capacity()); //10 System.out.println("------------"); //重置緩沖區 byteBuffer.rewind(); System.out.println("position位置:"+byteBuffer.position()); //0 System.out.println("limit上限:"+byteBuffer.limit()); //10 System.out.println("capacity容量:"+byteBuffer.capacity());//10
flip重置緩沖區
flip函數現將limit設置為position位置,再將position置為0位置,并清除mar標記。
//創建一個容量為10的緩沖區 ByteBuffer byteBuffer=ByteBuffer.allocate(10); //添加數據到緩沖區 byteBuffer.put("abcde".getBytes()); System.out.println("position位置:"+byteBuffer.position()); //5 System.out.println("limit上限:"+byteBuffer.limit()); //10 System.out.println("capacity容量:"+byteBuffer.capacity()); //10 System.out.println("------------"); //重置緩沖區 byteBuffer.flip(); System.out.println("position位置:"+byteBuffer.position()); //0 System.out.println("limit上限:"+byteBuffer.limit()); //5 System.out.println("capacity容量:"+byteBuffer.capacity());//10
clear清空緩沖區
clear方法也將position置為0,同時將limit置為capacity的大小,并清除mark標記。
//創建一個容量為10的緩沖區 ByteBuffer byteBuffer=ByteBuffer.allocate(10); //設置上限為5 byteBuffer.limit(5); //添加數據到緩沖區 byteBuffer.put("abcde".getBytes()); System.out.println("position位置:"+byteBuffer.position()); //5 System.out.println("limit上限:"+byteBuffer.limit()); //5 System.out.println("capacity容量:"+byteBuffer.capacity()); //10 System.out.println("------------"); //重置緩沖區 byteBuffer.clear(); System.out.println("position位置:"+byteBuffer.position()); //0 System.out.println("limit上限:"+byteBuffer.limit()); //10 System.out.println("capacity容量:"+byteBuffer.capacity());//10
標記和恢復
ByteBuffer buffer = ByteBuffer.allocate(10); //添加數據到緩沖區 buffer.put("abcde".getBytes()); //打一個標記 buffer.mark(); System.out.println("標記位置:"+buffer.position()); //5 //再添加5個字節數據到緩沖區 buffer.put("fijkl".getBytes()); System.out.println("當前位置:"+buffer.position()); //10 //將position恢復到mark標記位置 buffer.reset(); System.out.println("恢復標記位置:"+buffer.position());//5
FileChannel通道
FileChannel是用于操作文件的通道,可以用于讀取文件、也可以寫入文件
//創建讀取文件通道 FileChannel fisChannel = new FileInputStream("day05/src/a.txt").getChannel(); //創建寫入文件的通道 FileChannel fosChannel = new FileOutputStream("day05/src/b.txt").getChannel(); //創建緩沖區 ByteBuffer buffer = ByteBuffer.allocate(2); while (fisChannel.read(buffer)!=-1){ System.out.println("position:"+buffer.position()); //0 System.out.println("limit:"+buffer.limit());//2 //重置緩沖區(為輸出buffer數據做準備) buffer.flip(); fosChannel.write(buffer); //重置緩沖區(為輸入buffer數據做準備) buffer.clear(); } //關閉通道 fisChannel.close(); fosChannel.close();
SocketChannel通道
下面代碼使用SocketChannel通道上傳文件到服務器
public class Client { public static void main(String[] args) throws IOException { //創建通道 SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 10000)); //創建緩沖區 ByteBuffer buffer = ByteBuffer.allocate(1024); //讀取本地文件數據到緩沖區 FileChannel fisChannel = new FileInputStream("day05/src/a.txt").getChannel(); while (fisChannel.read(buffer)!=-1){ buffer.flip(); //為寫入數據做準備 socketChannel.write(buffer); buffer.clear(); //為讀取數據做準備 } //關閉本地通道 fisChannel.close(); //socketChannel.shutdownOutput(); //讀取服務端回寫的數據 buffer.clear(); int len = socketChannel.read(buffer); System.out.println(new String(buffer.array(), 0, len)); //關閉socket通道 socketChannel.close(); } }
ServerSocketChannel通道
下面代碼使用ServerSocketChannel通道接收文件并保存到服務器
public class Server { public static void main(String[] args) throws IOException { //1.創建ServerSocketChannel通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //2.綁定端口號 serverSocketChannel.bind(new InetSocketAddress(10000)); //3.設置非阻塞 serverSocketChannel.configureBlocking(false); System.out.println("服務器已開啟"); while (true) { //4.獲取客戶端通道,如果有客戶端連接返回客戶端通道,否則返回null SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannel!=null){ socketChannel.configureBlocking(false); //創建本地通道,用于往文件中寫數據 UUID uuid = UUID.randomUUID(); FileChannel fosChannel=new FileOutputStream("day05/src/"+uuid+".txt").getChannel(); ByteBuffer buffer=ByteBuffer.allocate(1024); while (socketChannel.read(buffer)>0){ buffer.flip(); //準備把緩沖區數據輸出 fosChannel.write(buffer); buffer.clear();//準備讀取數據到緩沖區 } fosChannel.close(); //回寫數據到客戶端 ByteBuffer resultBuffer=ByteBuffer.wrap("上傳文件成功".getBytes()); socketChannel.write(resultBuffer); //關閉客戶端通道 socketChannel.close(); } } } }
NIO Selector選擇器
Selector一般稱為選擇器,當然你也可以翻譯為 **多路復用器** 。它是Java NIO核心組件中的一個,用于檢查一個或多個NIO Channel(通道)的狀態是否處于可讀、可寫。如此可以實現單線程管理多個channels,也就是可以管理多個網絡鏈接。
使用Selector的服務器模板代碼
有了模板代碼我們在編寫程序時,大多數時間都是在模板代碼中添加相應的業務代碼
ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.socket().bind(new InetSocketAddress("localhost", 8080)); ssc.configureBlocking(false); Selector selector = Selector.open(); ssc.register(selector, SelectionKey.OP_ACCEPT); while(true) { int readyNum = selector.select(); if (readyNum == 0) { continue; } Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> it = selectedKeys.iterator(); while(it.hasNext()) { SelectionKey key = it.next(); if(key.isAcceptable()) { // 接受連接 } else if (key.isReadable()) { // 通道可讀 } else if (key.isWritable()) { // 通道可寫 } it.remove(); } }
NIO Selector服務端
按照上面的模板代碼,改寫接收文件的服務端。
public class Server { public static void main(String[] args) { try { ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.socket().bind(new InetSocketAddress("localhost", 10000)); ssc.configureBlocking(false); Selector selector = Selector.open(); ssc.register(selector, SelectionKey.OP_ACCEPT); FileChannel fosChannel=null; while (true) { int readyNum = selector.select(); System.out.println(readyNum); if (readyNum == 0) { continue; } Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> it = selectedKeys.iterator(); while (it.hasNext()) { SelectionKey key = it.next(); SocketChannel socketChannel1=null; SocketChannel socketChannel2=null; if (key.isAcceptable()) { System.out.println("isAcceptable"); // 創建新的連接,并且把連接注冊到selector上,而且, // 聲明這個channel只對讀操作感興趣。 socketChannel1 = ssc.accept(); socketChannel1.configureBlocking(false); socketChannel1.register(selector, SelectionKey.OP_READ); UUID uuid = UUID.randomUUID(); fosChannel=new FileOutputStream("day05/src/"+uuid+".txt").getChannel(); } else if (key.isReadable()) { System.out.println("isReadable"); // 通道可讀 socketChannel2 = (SocketChannel) key.channel(); //創建本地通道,用于往文件中寫數據 ByteBuffer readBuff=ByteBuffer.allocate(1024); while (socketChannel2.read(readBuff)>0){ readBuff.flip(); //準備把緩沖區數據輸出 fosChannel.write(readBuff); readBuff.clear();//準備讀取數據到緩沖區 } fosChannel.close(); key.interestOps(SelectionKey.OP_WRITE); } else if (key.isWritable()) { System.out.println("isWritable"); // 通道可寫 ByteBuffer writeBuff=ByteBuffer.allocate(1024); writeBuff.put("上傳成功".getBytes()); writeBuff.flip(); SocketChannel socketChannel = (SocketChannel) key.channel(); socketChannel.write(writeBuff); key.interestOps(SelectionKey.OP_READ); } it.remove(); } } } catch (Exception e) { //e.printStackTrace(); } finally { } } }