一.概述
1.NIO是什么?
NIO,有人叫New IO,因为它是jdk1.4之后才加进来的。但是其实更加准确的定义是non-blocking IO,就是非阻塞IO。总所周知,传统的IO操作是阻塞的,效率不高,浪费cpu的性能。NIO的到来就解决了这个问题。
2.BIO,NIO,AIO?
BIO:同步阻塞,其实就是BlockingIO,也就是传统的那些IO操作。同步是说,当服务器执行去磁盘执行read操作时,要自己去看有没有成功获取数据,然后如果没有数据返回就一直在等,边等边查数据是否准备好了,服务器(程序)是干不了其他事情的。一般以前的IO为了处理高并发,都是为每一个IO操作请求建立一个线程来干IO操作,好让服务器cpu可以干其他事情。
NIO:同步非阻塞,同步是说还是一样,程序要定时去读取磁盘,这里有选择器里面的轮询搞掂,然后非阻塞是说执行read操作时,不过有没有数据,都会马上返回,好让服务器(程序)可以干其他线程,这样服务器就不用创建很多线程去操作IO了。
AIO:异步非阻塞,AIO是发出IO请求后,由操作系统自己去获取IO权限并进行IO操作,成功了就通知程序,说数据搞好了,这个时候程序再调用回调函数处理数据,异步是指由操作系统通知程序,非阻塞是调用read方法没有数据也返回,程序继续往下执行。
二.NIO三大利器
1.缓冲区
核心方法:
allocate:分配指定空间大小的缓冲区。
put和get:获取缓冲区的数据。
rewind:重置position位置,读写模式都行。
flip:写转读模式,原来写的位置position变为读的上限limit,position置0.
clear:limit变为capactity,position变为0,写模式。
核心变量:
capactity:最大容量,一般为常量。
limit:数据有效的上限。
mark:记录当前位置position。
position:当前位置。
缓冲分类:
直接缓冲:是指分配到物理内存的缓冲区,通过allocateDirect来分配
间接缓冲:分配在堆内的缓冲,其实就是数组啦,通过allocate分配
判别方法:isDirect();
2.通道
1.作用:负责缓冲区的数据的传输,本身不包含数据,要和Buffer一起共用。
2.实现类:FileChannel,SocketChannel,ServerSocketChanel,DatapramChannel。
3.具体使用:通过getChannel获取一个Channel
本地IO:FileInputStream,FileOutputStream,RandomAccess
网络IO:Socket,ServerSocket,DatagramSocket
3.Selector选择器
1.作用:一个多路复用选择器,通过轮询监听通道的信息,它是NIO实现非阻塞的关键。可以看一下NIO的HelloWord里面它的作用,代码如下:
//4.获取选择器 Selector selector = Selector.open(); //5.注册通道到选择器 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //6.轮询式获取选择器准备就绪的事件 while(selector.select()>0){ //7.获取选择器里面的事件的 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()){ //8.获取选择器里面的事件 SelectionKey sk = iterator.next(); if (sk.isAcceptable()){ //9.接收客户端的socket SocketChannel clientAcceptChannel = serverSocketChannel.accept(); //10.设置为非阻塞模式 clientAcceptChannel.configureBlocking(false); //11.设置这个socketChannel为准备读状态,注册到选择器上面 clientAcceptChannel.register(selector,SelectionKey.OP_READ); }else if(sk.isReadable()){ //12.获取当前选择器上面状态为可读的通道 SocketChannel socketReadChannel = (SocketChannel) sk.channel(); //13.开始往读取客户端那个通道的数据 ByteBuffer readClientBuffer = ByteBuffer.allocate(1024); int len = 0; while((len=socketReadChannel.read(readClientBuffer))>0){ readClientBuffer.flip(); //14.输入客户端传的数据到控制台 System.out.println(new String(readClientBuffer.array(),0,len)); readClientBuffer.clear(); } } iterator.remove(); }
四.NIO实战
下面这个NIO实战小Demo是模拟一个简易的聊天室,有服务端程序Server,也有Client程序,客户端创建通道,缓冲区,从键盘输入获取数据传送给服务端。服务端则是一样,创建通道,绑定端口,注册通道到选择器,然后最重要是轮询,把注册到选择器里面的通道拿出来进行状态判断,进行相应的操作,具体代码看下面。
Server端
public class Server { public static void main(String[] args) throws IOException { Server server = new Server(); server.receive(); } public void receive() throws IOException { //1.创建通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //2.切换到非阻塞模式 serverSocketChannel.configureBlocking(false); //3.绑定端口 serverSocketChannel.bind(new InetSocketAddress(8089)); //4.获取选择器 Selector selector = Selector.open(); //5.注册通道到选择器 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //6.轮询式获取选择器准备就绪的事件 while(selector.select()>0){ //7.获取选择器里面的事件的 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()){ //8.获取选择器里面的事件 SelectionKey sk = iterator.next(); if (sk.isAcceptable()){ //9.接收客户端的socket SocketChannel clientAcceptChannel = serverSocketChannel.accept(); //10.设置为非阻塞模式 clientAcceptChannel.configureBlocking(false); //11.设置这个socketChannel为准备读状态,注册到选择器上面 clientAcceptChannel.register(selector,SelectionKey.OP_READ); }else if(sk.isReadable()){ //12.获取当前选择器上面状态为可读的通道 SocketChannel socketReadChannel = (SocketChannel) sk.channel(); //13.开始往读取客户端那个通道的数据 ByteBuffer readClientBuffer = ByteBuffer.allocate(1024); int len = 0; while((len=socketReadChannel.read(readClientBuffer))>0){ readClientBuffer.flip(); //14.输入客户端传的数据到控制台 System.out.println(new String(readClientBuffer.array(),0,len)); readClientBuffer.clear(); } } iterator.remove(); } } } }
Client端
public class Client { public static void main(String[] args) throws IOException { Client client = new Client(); client.send(); } private void send() throws IOException{ //1.获取SocketChannel通道 SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8089)); //2.切换到非阻塞模式 socketChannel.configureBlocking(false); //3.创建缓冲区 ByteBuffer clientBuffer = ByteBuffer.allocate(1024); //4.发送数据给服务端 Scanner scanner = new Scanner(System.in); while (scanner.hasNext()){ String str = scanner.next(); clientBuffer.put((new Date().toString()+"-----锐鑫-----"+"\n"+str).getBytes()); clientBuffer.flip(); socketChannel.write(clientBuffer); clientBuffer.clear(); } socketChannel.close(); } }