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