NIO
一、NIO
1.1 概述
NIO全程Java Non-blocking IO或Java New IO,是从JDK1.4开始引入的一套全新的IO,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络
- BIO:阻塞式编程
- NIO:非阻塞式编程
- AIO(NIO2) Asynchronous:非阻塞异步网络编程
IO操作模式
- PIO(Programing IO)
- 所有的IO操作由NICPU处理,CPU占用率比较高
- DMA(Direct Memory Access)
- CPU把IO操作控制权交给DMA控制,只能以固定的方式读写,CPU空闲做其他工作
- 通道方式(Channel)
- 能执行优先通道指令的IO控制器,代替CPU管理控制外设
- 通道由自己的指令系统,式一个协处理器,具有更强的独立处理数据输入和输出的能力
1.2 NIO组成
- Buffer:缓冲区
- Channel:通道
- Selector:选择器(轮询器)
NIO和普通IO的区别
普通IO | NIO |
---|---|
面向流(Stream Oriented) | 面向缓冲区(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
无 | 选择器(Selector) |
1.2.1 Buffer缓冲区
- Java NIO中的Buffer用于和NIO通道进行交互
- 缓冲区本质上是一块可以写入数据,也可以从中读取数据的内存
- Buffer是一个抽象类,子类包括:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- 由于子类也是抽象类,所以不能实例化对象
Buffer基本使用
- 创建缓冲区
- 间接缓冲区
- 在堆中开辟,易于管理,垃圾回收器可以回收,空间有限,读写文件速度较慢
- 直接缓冲区
- 直接在物理内存中开辟空间,空间比较大,读写文件速度快
- 缺点:不受垃圾回收器控制,创建和销毁耗性能
- 间接缓冲区
- 调用put方法写入数据到Buffer
- 调用flip()方法
- 调用get方法从Buffer中读取数据
- 调用clear()方法或者compact()方法
1.2.2 Buffer核心方法
方法名 | 描述 |
---|---|
static ByteBuffer allocate(int capacity) | 在堆中开辟空间,间接缓冲区 |
ByteBuffer put(byte[] src) | 在直接内存开辟空间,直接缓冲区 |
Buffer flip() | flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值 |
ByteBuffer get(byte[] dst) | 使用get()方法从Buffer中读取数据 |
Buffer clear() | 清空缓冲区,position将被设回0,limit被设置成capacity的值 |
ByteBuffer compact() | compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素 |
- Buffer三个重要属性
- capacity
- position
- limit
1.2.3 Channel通道
- Channel类似流。数据可以从Channel读到Buffer中,也可以从Buffer写到Channel中
- Channel接口主要实现类
- FileChannel
- ServerSocketChannel
- SocketChannel
- DatagramChannel
1.2.4 FileChannel
- Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。
- FileChannel无法设置为非阻塞模式。
- 创建方式三种
- 使用文件字节流或RandomAccessFile来获取一个FileChannel实例
- 使用Channels工具类
- JDK1.7后才能使用,FileChannel.open()方法
public class CreateFileChannel {
public static void main(String[] args) throws Exception {
//方式一:
FileChannel fileChannel = FileChannel.open(Paths.get("D:\\aa.txt"), StandardOpenOption.READ);
//方式二:
FileChannel fileChannel1 = new RandomAccessFile("D:\\aa.txt","rw" ).getChannel();
//方式三:
FileChannel fileChannel2 = new FileInputStream("D:\\aa.txt").getChannel();
}
}
//文件复制
public class CopyFile {
public static void main(String[] args) throws Exception{
FileChannel file = new FileInputStream("D:\\aa.txt").getChannel();
FileChannel file1 = new FileOutputStream("D:\\copyaa.txt").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(file.read(buffer)>0){
buffer.flip();
file1.write(buffer);
buffer.clear();
}
file1.close();
file.close();
}
}
//解码器
public class FileChannelDemo {
public static void main(String[] args) throws Exception{
//创建FileChannel
// FileChannel fileChannel = new FileInputStream("D:\\aa.txt",).getChannel();
FileChannel channel = new RandomAccessFile("D:\\aaa.txt", "r").getChannel();
//创建解码器
CharsetDecoder decoder = Charset.forName("utf-8").newDecoder();
//创建字符传冲区
CharBuffer charBuffer = CharBuffer.allocate(3);
ByteBuffer buffer = ByteBuffer.allocate(3);
System.out.println(""+buffer.limit()+"--->"+buffer.position());
while(channel.read(buffer)>0){
buffer.flip();
// System.out.println("反转"+buffer.limit()+"--->"+buffer.position());
// System.out.println(new String(buffer.array(),0,buffer.limit(),"UTF-8"));
decoder.decode(buffer, charBuffer, false);
charBuffer.flip();
String data = charBuffer.toString();
System.out.println(data);
buffer.compact();
charBuffer.clear();
// buffer.clear();
}
// channel.close();
}
}
1.2.4.1 内存映射文件
- 直接缓冲区的使用,可以提高读写的速度。但是直接缓冲区的创建和销魂的开销比较大,一般大文件操作或能显著提高读写性能时使用
- 内存映射文件也属于直接缓冲区
public class Demo01 {
public static void main(String[] args) throws Exception{
//创建文件通道
FileChannel in = new RandomAccessFile("D:\\11.jpg", "rw").getChannel();
FileChannel out = new RandomAccessFile("D:\\ddd11.jpg","rw").getChannel();
MappedByteBuffer map = in.map(FileChannel.MapMode.READ_ONLY, 0, in.size());
out.write(map);
out.close();
in.close();
}
}
1.2 NIO实现网络编程
-
NIO中提供了实现非阻塞式网络编程API
-
ServerSocketChannel
- ServerSocketChannel是一个基于通道的socket监听器,等同于ServerSocket类
-
SocketChannel
- SocketChannel是一个基于通道的客户端套接字,等同于Socket类
-
NIO也支持阻塞时网络编程
public class ServerDemo {
public static void main(String[] args) throws Exception{
//服务器
ServerSocketChannel server = ServerSocketChannel.open();
//绑定端口号,地址
server.bind(new InetSocketAddress("10.9.62.210",8888));
//侦听客户端SocketChannel
SocketChannel channel = server.accept();
//数据处理
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
buffer.flip();
String data = new String(buffer.array(), 0, buffer.limit());
System.out.println(channel.getRemoteAddress()+"说:"+data);
// buffer.clear();
buffer.put("你好".getBytes());
buffer.flip();
channel.write(buffer);
//关闭
buffer.clear();
server.close();
}
}
public class ClientDemo {
public static void main(String[] args) throws Exception{
// ServerSocketChannel socketChannel = ServerSocketChannel.open();
// socketChannel.bind(new InetSocketAddress("10.9.62.210",8888));
//创建SocketChannel
SocketChannel channel = SocketChannel.open(new InetSocketAddress("10.9.62.210",8888));
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("好久不见".getBytes());
buffer.flip();
channel.write(buffer);
buffer.flip();
channel.read(buffer);
buffer.flip();
String data = new String(buffer.array(),0,buffer.limit());
System.out.println(channel.getRemoteAddress()+"shuo:"+data);
//guanbi
buffer.clear();
channel.close();
}
}
二、 Selector
- Selectot提供了询问通道是否已经准备号执行每个I/O操作的能力
- Selector允许单线程处理多个Channel
- 仅用单个线程来处理多个Channel的好处是,只需要更少的线程来处理通道。这样会大量的减少线程上下文切换的开销
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IaatwBbb-1617807085630)(C:\Users\Evan\AppData\Roaming\Typora\typora-user-images\image-20210407213429272.png)]
- Selector
- 选择器类管理着一个被注册的通道集合的信息和他们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态
- SelectorKey
- 选择键封装了特定的通道与特定的选择器的注册关系
- 选择键支持四种操作类型
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
//服务器
public class ChatServer {
public static void main(String[] args) throws Exception{
//1.创建ServerSocketChannel
ServerSocketChannel server = ServerSocketChannel.open();
//2.绑定IP和端口号
server.bind(new InetSocketAddress("10.9.62.210", 6666));
//3.设置为非阻塞模式
server.configureBlocking(false);
//4.创建轮询器
Selector selector = Selector.open();
//5.在server注册到轮询器上
server.register(selector, SelectionKey.OP_ACCEPT);
//6.轮询处理(阻塞方法),返回键的个数
while(selector.select()>0){
//7.获取所有选择键
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next();
//8.如果有客户连接
if(key.isAcceptable()){
//9.接收请求,不是阻塞方法
SocketChannel accept = server.accept();
System.out.println(accept.getRemoteAddress()+"进入了聊天室");
//10.设置非阻塞模式
accept.configureBlocking(false);
//11.注册selector
accept.register(selector, SelectionKey.OP_READ);
}else if(key.isReadable()){
//读取数据
//12.根据key,获取SocketChannel
SocketChannel channel = (SocketChannel)key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len;
try {
while((len=channel.read(buffer))>0){//如果客户端没有正常关闭,出现异常
buffer.flip();
String data = new String(buffer.array(),0,buffer.limit());
System.out.println(channel.getRemoteAddress()+"说:"+data);
buffer.clear();
}
if(len==-1){//正常结束
System.out.println(channel.getRemoteAddress()+"退出了聊天室");
channel.close();
}
} catch (IOException e) {
System.out.println(channel.getRemoteAddress()+"异常退出了聊天室");
channel.close();
}
}
iterator.remove();
}
}
}
}
//客户端
public class ChatClient {
public static void main(String[] args) throws Exception{
//1.创建SocketChannel
SocketChannel client = SocketChannel.open(new InetSocketAddress("10.9.62.210", 6666));
//2.设置非阻塞
client.configureBlocking(false);
Scanner scanner = new Scanner(System.in);
//3.处理
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(true){
String data = scanner.next();
buffer.put(data.getBytes());
buffer.flip();
client.write(buffer);
buffer.clear();
if("886".equals(data)||"byebye".equals(data)){
break;
}
}
buffer.clear();
client.close();
}
}