简介
java.nio全称java non-blocking IO(实际上是 new io),是指JDK 1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。
HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。
IO和NIO的区别
原有的 IO 是面向流的、阻塞的,NIO 则是面向块的、非阻塞的。
IO是面向流的、阻塞的
java1.4以前的io模型,一连接对一个线程。
原始的IO是面向流的,不存在缓存的概念。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区
Java IO的各种流是阻塞的,这意味着当一个线程调用read或 write方法时,该线程被阻塞,直到有一些数据被读取,或数据完全写入,该线程在此期间不能再干任何事情了。
NIO 是面向块的、非阻塞的。
NIO是面向缓冲区的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性。
Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。
NIO的核心实现
在标准IO API中,你可以操作字节流和字符流,但在新IO中,你可以操作通道和缓冲,数据总是从通道被读取到缓冲中或者从缓冲写入到通道中。
NIO核心API Channel, Buffer, Selector
通道Channel
NIO的通道类似于流,但有些区别如下:
-
通道可以同时进行读写,而流只能读或者只能写
-
通道可以实现异步读写数据
-
通道可以从缓冲读数据,也可以写数据到缓冲:
缓存Buffer
缓冲区本质上是一个可以写入数据的内存块,然后可以再次读取,该对象提供了一组方法,可以更轻松地使用内存块,使用缓冲区读取和写入数据通常遵循以下四个步骤:
-
写数据到缓冲区;
-
调用buffer.flip()方法;
-
从缓冲区中读取数据;
-
调用buffer.clear()或buffer.compat()方法;
当向buffer写入数据时,buffer会记录下写了多少数据,一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式,在读模式下可以读取之前写入到buffer的所有数据,一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。
Buffer在与Channel交互时,需要一些标志:
buffer的大小/容量 - Capacity
作为一个内存块,Buffer有一个固定的大小值,用参数capacity表示。
当前读/写的位置 - Position
当写数据到缓冲时,position表示当前待写入的位置,position最大可为capacity – 1;当从缓冲读取数据时,position表示从当前位置读取。
信息末尾的位置 - limit
在写模式下,缓冲区的limit表示你最多能往Buffer里写多少数据; 写模式下,limit等于Buffer的capacity,意味着你还能从缓冲区获取多少数据。
缓冲区常用的操作
向缓冲区写数据:
1. 从Channel写到Buffer;
2. 通过Buffer的put方法写到Buffer中;
从缓冲区读取数据:
1. 从Buffer中读取数据到Channel;
2. 通过Buffer的get方法从Buffer中读取数据;
flip方法:
将Buffer从写模式切换到读模式,将position值重置为0,limit的值设置为之前position的值;
clear方法 vs compact方法:
clear方法清空缓冲区;compact方法只会清空已读取的数据,而还未读取的数据继续保存在Buffer中;
Selector
一个组件,可以检测多个NIO channel,看看读或者写事件是否就绪。
多个Channel以事件的方式可以注册到同一个Selector,从而达到用一个线程处理多个请求成为可能。
文件NIO
文件写入
//1、创建输出流
FileOutputStream fos=new FileOutputStream("name.txt");
//2、从流中得到一个通道
FileChannel fc=fos.getChannel();
//3、提供一个缓冲区
ByteBuffer buffer =ByteBuffer.allocate(1024);
//4、往缓冲区中存入数据
String str="hello,nio";
buffer.put(str.getBytes());
//5、翻转缓冲区
buffer.flip();
//6、把缓冲区写到通道中
fc.write(buffer);
//7、关闭
fos.close();
文件读取
File file = new File("name.txt");
//1、创建输入流
FileInputStream fis=new FileInputStream(file);
//2、从流中得到一个通道
FileChannel fc=fis.getChannel();
//3、提供一个缓冲区
ByteBuffer buffer =ByteBuffer.allocate((int)file.length());
//4、从通道里读取数据并存到缓冲区中
fc.read(buffer);
System.out.println(new String(buffer.array()));
//7、关闭
fis.close();
文件复制
//1、创建两个流
FileInputStream fis=new FileInputStream("name.txt");
FileOutputStream fos=new FileOutputStream("newName.txt");
//2、得到两个通道
FileChannel sourceFC =fis.getChannel();
FileChannel destFC=fos.getChannel();
//3、复制
destFC.transferFrom(sourceFC,0,sourceFC.size());
//4、关闭
fis.close();
fos.close();
网络NIO
通过案例来了解网络NIO用法
网络服务端程序
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
/**
* @author liuzonghua
* @Package top.maniy.util.nio
* @Description: 网络服务端程序
* @date 2020/4/26 21:45
*/
public class NIOServer {
public static void main(String[] args) throws Exception{
//1.得到一个ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.得到一个Selector对象
Selector selector = Selector.open();
//3.绑定一个端口号
serverSocketChannel.bind(new InetSocketAddress(9999));
//4.设置非阻塞方式
serverSocketChannel.configureBlocking(false);
//5.把serverSocketChannel对象注册给Selector对象,监控客户端
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//6,处理事情
while(true){
//6.1监控客户端
if(selector.select(2000) == 0) {//监控客户端超时时间,0个客户端
System.out.println("同时可以做其他事情。");
continue;
}
//6.2得到SelectionKey,判断通道里的事件
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()){
SelectionKey key =keyIterator.next();
if(key.isAcceptable()){ //客户端连接事件
System.out.println("OP_ACCEPT");
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));//重点监控读事件,第三公个是附件
}
if (key.isReadable()){ //读取客户端数据事件
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment(); //附件为缓存区
channel.read(buffer);
System.out.println("客户端发来数据:"+new String(buffer.array()));
}
//6.3 手动从集合中移除当前key,防治重复处理
keyIterator.remove();
}
}
}
}
网络客户端程序
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* @author liuzonghua
* @Package top.maniy.util.nio
* @Description:网络客户端程序
* @date 2020/4/26 21:32
*/
public class NIOClient {
public static void main(String[] args) throws Exception {
//1、得到一个网络通道
SocketChannel channel = SocketChannel.open();
//2、设置非阻塞方式
channel.configureBlocking(false);
//3、提供服务器的IP地址和端口号
InetSocketAddress address = new InetSocketAddress("127.0.0.1",9999);
//4、连接服务器端
if(!channel.connect(address)){//初次连接失败
while (!channel.finishConnect()){//一直连接到,连接成功
System.out.println("连接的同时可以做其他事情。");
}
}
//5.得到一个缓存区并存入数据
String msg = "info";
ByteBuffer writeBuf = ByteBuffer.wrap(msg.getBytes());
//6.发送数据
channel.write(writeBuf);
System.in.read();
}
}
NIO2(AIO)
AIO是异步IO的缩写,虽然NIO在网络操作中,提供了非阻塞的方法,但是NIO的IO行为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程自行进行IO操作,IO操作本身是同步的。
但是对AIO来说,则更加进了一步,它不是在IO准备好时再通知线程,而是在IO操作已经完成后,再给线程发出通知。因此AIO是不会阻塞的,此时我们的业务逻辑将变成一个回调函数,等待IO操作完成后,由系统自动触发。
与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 在JDK1.7中,这部分内容被称作NIO.2。