> 文章列表 > JAVA NIO简解

JAVA NIO简解

JAVA NIO简解

1. 了解NIO

在这里插入图片描述

1.1.什么是NIO?

  1. Java nio是Java的一个新的输入输出(NewInput/Output)API,它提供了一些高效的数据处理方式,如缓冲区(buffers)、字符集(charsets)、通道(channels)和选择器(selectors)。
  2. Java NIO可以实现非阻塞式的多路复用输入输出,提高了程序的性能和可扩展性。Java nio是在Java 1.4版本中引入的,后来在Java7中又增加了NIO2,增加了一些新的特性,如文件系统、异步通道和文件监视等。Java NIO的主要类都在java NIO包和其子包中定义。

1.1.NIO和传统IO的区别

    1. JAVA IO是面向流的,JAVA nio是面向缓冲区的。面向流的意思是数据是一个字节一个字节地传输的,面向缓冲区的意思是数据是以块为单位传输的。
    1. JAVA IO是阻塞式的,JAVA nio是非阻塞式的。阻塞式的意思是当一个线程进行读写操作时,它会一直等待直到操作完成,而非阻塞式的意思是当一个线程进行读写操作时,它可以同时进行其他操作,当读写操作完成时会通知线程。
    1. JAVA IO主要有三种类型:字节流、字符流和对象流,JAVA nio主要有三种类型:缓冲区、通道和选择器。缓冲区是用于存储数据的容器,通道是用于连接IO设备的连接,选择器是用于管理多个通道的工具。
    1. JAVA IO主要使用装饰者模式来实现各种功能,JAVA nio主要使用适配器模式来实现各种功能。装饰者模式是通过包装原有的对象来增加新的功能,适配器模式是通过转换原有的对象来适应新的接口。

2.简单体验

ByteBuffer buf = ByteBuffer.allocateDirect(1024) 是在直接内存中的buff,速度更快,零地址转换

2.1.通道 – 读写数据

1.简单读写

//创建一个文件通道
FileChannel fileChannel = FileChannel.open(Paths.get("test.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE);//创建一个缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);//从文件通道读取数据到缓冲区
int bytesRead = fileChannel.read(buffer);//切换缓冲区到读模式
buffer.flip();//从缓冲区读取数据并打印
while (buffer.hasRemaining()) {System.out.print((char) buffer.get());
}//清空缓冲区
buffer.clear();//向缓冲区写入数据
buffer.put("Hello, NIO!".getBytes());//切换缓冲区到写模式
buffer.flip();//从缓冲区写入数据到文件通道
int bytesWritten = fileChannel.write(buffer);//关闭文件通道
fileChannel.close();

2.发散聚集

Nio的发散聚集是指使用一个通道分别读写多个缓冲区或者使用多个通道分别读写一个缓冲区的操作。Nio的发散聚集的代码操作如下

//创建一个随机访问文件
RandomAccessFile file = new RandomAccessFile("test.txt", "rw");//获取文件通道
FileChannel fileChannel = file.getChannel();//创建两个缓冲区
ByteBuffer buffer1 = ByteBuffer.allocate(10);
ByteBuffer buffer2 = ByteBuffer.allocate(20);//将两个缓冲区放入一个数组
ByteBuffer[] buffers = {buffer1, buffer2};//从文件通道读取数据到两个缓冲区,这是发散操作
fileChannel.read(buffers);//切换缓冲区到读模式
buffer1.flip();
buffer2.flip();//从两个缓冲区读取数据并打印
while (buffer1.hasRemaining()) {System.out.print((char) buffer1.get());
}
System.out.println();
while (buffer2.hasRemaining()) {System.out.print((char) buffer2.get());
}
System.out.println();//清空缓冲区
buffer1.clear();
buffer2.clear();//向两个缓冲区写入数据
buffer1.put("Hello".getBytes());
buffer2.put("World".getBytes());//切换缓冲区到写模式
buffer1.flip();
buffer2.flip();//将两个缓冲区的数据写入到文件通道,这是聚集操作
fileChannel.write(buffers);//关闭文件通道和随机访问文件
fileChannel.close();
file.close();

3.FileChannel内存映射

FileChannel内存映射文件是指将文件的一部分或全部映射到直接内存中,这样可以提高文件的访问效率,避免了数据在操作系统内存和JVM内存之间的拷贝123。FileChannel内存映射文件的代码操作如下:

//创建一个随机访问文件
RandomAccessFile file = new RandomAccessFile("test.txt", "rw");//获取文件通道
FileChannel fileChannel = file.getChannel();//将文件的一部分映射到直接内存中,返回一个MappedByteBuffer对象
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, file.length());//读取或修改直接内存中的数据
byte[] data = new byte[mappedByteBuffer.limit()];
mappedByteBuffer.get(data);
System.out.println(new String(data));
mappedByteBuffer.put(0, (byte) 'H');//关闭文件通道和随机访问文件
fileChannel.close();
file.close();

2.2.管道(Pipe)–线程通信

java NIO
管道是两个线程之间的单向数据连接,有一个source通道和一个sink通道,数据会被写到sink通道,从source通道读取

//创建管道
Pipe pipe = Pipe.open();//获取sink通道
Pipe.SinkChannel sinkChannel = pipe.sink();//创建缓冲区并写入数据
ByteBuffer buffer = ByteBuffer.allocate(48);
buffer.put("Hello, world!".getBytes());
buffer.flip();//将缓冲区的数据写入到sink通道
while (buffer.hasRemaining()) {sinkChannel.write(buffer);
}//获取source通道
Pipe.SourceChannel sourceChannel = pipe.source();//创建缓冲区并从source通道读取数据
buffer.clear();
int bytesRead = sourceChannel.read(buffer);//打印缓冲区的数据
buffer.flip();
while (buffer.hasRemaining()) {System.out.print((char) buffer.get());
}

2.3.网络IO

  1. 服务端代码
//服务端代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;public class NioServer {public static void main(String[] args) throws IOException {//创建一个ServerSocketChannel对象ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//设置为非阻塞模式serverSocketChannel.configureBlocking(false);//绑定一个端口号serverSocketChannel.bind(new InetSocketAddress(8888));//创建一个Selector对象Selector selector = Selector.open();//将ServerSocketChannel注册到Selector上serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//在一个循环中调用Selector的select方法while (true) {//获取已经就绪的通道集合int readyChannels = selector.select();if (readyChannels == 0) continue;//遍历就绪的通道集合Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {//获取当前的SelectionKeySelectionKey selectionKey = iterator.next();//根据不同的事件类型进行处理if (selectionKey.isAcceptable()) {//如果是OP_ACCEPT事件//获取对应的SocketChannel对象SocketChannel socketChannel = serverSocketChannel.accept();//设置为非阻塞模式socketChannel.configureBlocking(false);//注册到Selector上,关注OP_READ事件socketChannel.register(selector, SelectionKey.OP_READ);} else if (selectionKey.isReadable()) {//如果是OP_READ事件//获取对应的SocketChannel对象SocketChannel socketChannel = (SocketChannel) selectionKey.channel();//创建一个ByteBuffer对象,用于存储读取到的数据ByteBuffer buffer = ByteBuffer.allocate(1024);//从通道中读取数据到缓冲区int len = socketChannel.read(buffer);//判断是否读到了数据if (len > 0) {//切换缓冲区到读模式buffer.flip();//打印读取到的数据System.out.println("Server received: " + new String(buffer.array(), 0, len));}} else if (selectionKey.isWritable()) {//如果是OP_WRITE事件//获取对应的SocketChannel对象SocketChannel socketChannel = (SocketChannel) selectionKey.channel();//创建一个ByteBuffer对象,用于存储要写入的数据ByteBuffer buffer = ByteBuffer.wrap("Hello, client".getBytes());//将缓冲区的数据写入到通道中socketChannel.write(buffer);}//移除当前的SelectionKey,避免重复处理iterator.remove();}}}
}
  1. 客户端
//客户端代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;public class NioClient {public static void main(String[] args) throws IOException {//创建一个SocketChannel对象SocketChannel socketChannel = SocketChannel.open();//设置为非阻塞模式socketChannel.configureBlocking(false);//连接到服务器的地址和端口号socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));//创建一个Selector对象Selector selector = Selector.open();//注册到Selector上,关注OP_CONNECT事件socketChannel.register(selector, SelectionKey.OP_CONNECT);//在一个循环中调用Selector的select方法while (true) {//获取已经就绪的通道集合int readyChannels = selector.select();if (readyChannels == 0) continue;//遍历就绪的通道集合Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {//获取当前的SelectionKeySelectionKey selectionKey = iterator.next();//根据不同的事件类型进行处理if (selectionKey.isConnectable()) {//如果是OP_CONNECT事件//获取对应的SocketChannel对象SocketChannel channel = (SocketChannel) selectionKey.channel();//如果连接已经完成if (channel.finishConnect()) {//注册到Selector上,关注OP_WRITE事件channel.register(selector, SelectionKey.OP_WRITE);}} else if (selectionKey.isWritable()) {//如果是OP_WRITE事件//获取对应的SocketChannel对象SocketChannel channel = (SocketChannel) selectionKey.channel();//创建一个ByteBuffer对象,用于存储要写入的数据ByteBuffer buffer = ByteBuffer.wrap("Hello, server".getBytes());//将缓冲区的数据写入到通道中channel.write(buffer);//注册到Selector上,关注OP_READ事件channel.register(selector, SelectionKey.OP_READ);} else if (selectionKey.isReadable()) {//如果是OP_READ事件//获取对应的SocketChannel对象SocketChannel channel = (SocketChannel) selectionKey.channel();//创建一个ByteBuffer对象,用于存储读取到的数据ByteBuffer buffer = ByteBuffer.allocate(1024);//从通道中读取数据到缓冲区int len = channel.read(buffer);//判断是否读到了数据if (len > 0) {//切换缓冲区到读模式buffer.flip();//打印读取到的数据System.out.println("Client received: " + new String(buffer.array(), 0, len));}}//移除当前的SelectionKey,避免重复处理iterator.remove();}}}
}