Java的IO、NIO、AIO了解

本文介绍了Java的IO、NIO和AIO。BIO是同步阻塞IO,适合简单场景;NIO提供Channel、Buffer和Selector,实现同步非阻塞,适合多路复用;AIO,又称NIO2,实现异步非阻塞,基于事件和回调,提升性能。

java提供了IO、NIO、AIO包,用于处理输入输出。

BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制
 

IO

IO就是我们常用的 java.io包,它基于流模型,提供了File 抽象、输入输出流等。因为是同步阻塞的,也有人称为BIO。好处就是可靠,返回后可以立刻知晓结果。
按数据分:字符流(InputStream、OutputStream)、字节流。
按操作分:输入流、输出流。

注:IO流的缓冲区是用来提高效率的。

 

NIO

NIO的三个主要组成部分:Channel(通道)、Buffer(缓冲区)、Selector(选择器)。它们关系可以简单下图说明:

 

Channel

可以通过它读取和写入数据,类似IO中的流,不过它可以用于读、写或者同时用于读写。一个Channel绑定一个Buffer。
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel

Buffer

NIO有如下 buffer:
ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer
这些Buffer类型代表了不同的数据类型。换句话说,就是可以通过char,short,int,long,float 或 double类型来操作缓冲区中的字节。

Selector

Selector 对象上可以注册很多个Channel,监听各个Channel上发生的事件,当事件发生时决定Channel读写。这样使用Selector就可以做到用一个线程来处理所有的channels,毕竟线程之间的切换的代价很高。

创建selector

Selector selector = Selector.open();

注册channel的事件

channel.configureBlocking(false);// 注意这里channel一定要设置成异步模式,不然报错
SelectionKey key = channel.register(selector,SelectionKey.OP_READ);

对应事件说明:

事件

功能

对应判断方法

SelectionKey.OP_ACCEP

服务器监听到了客户连接,服务器可以接收这个连接

selectionKey.isAcceptable();

SelectionKey.OP_CONNECT

连接就绪事件,表示客户端与服务器的连接已经建立成功

selectionKey.isConnectable();

SelectionKey.OP_READ

读就绪事件,表示通道中已经有了可读的数据,可以执行读操作

selectionKey.isReadable();

SelectionKey.OP_WRITE

写就绪事件,表示已经可以向通道写数据

selectionKey.isWritable();

如果要处理比较大的数据,从性能考虑建议用MappedByteBuffer。

看下示例:

public class MyMain {

    public static void main(String[] args) throws IOException {

        ByteBuffer sendbuffer = ByteBuffer.allocate(2048);
        ByteBuffer receivebuffer = ByteBuffer.allocate(2048);

        // 1 创建一个 Selector用于注册。当事件发生时,seletor 告诉你发生了什么
        Selector selector = Selector.open();

        ServerSocketChannel socketChannel = ServerSocketChannel.open();
        socketChannel.configureBlocking(false);//  注意:一定要设置为 非阻塞的

        ServerSocket socket = socketChannel.socket();
        socket.bind(new InetSocketAddress("127.0.0.1", 2181));// 绑定到给定的端口

        // 注册 accept 事件。值得一说的是因为Channel一定要是异步的,因此这里不能用 FileChannel
        socketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            // 这个方法会阻塞,直到至少有一个已注册的事件发生
            selector.select();
            // 返回此选择器的已选择键集。
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                handleKey(selector, selectionKey, sendbuffer, receivebuffer);
            }
        }
    }

    // 处理请求
    private static void handleKey(Selector selector, SelectionKey selectionKey, ByteBuffer sendBuffer, ByteBuffer receivebuffer) throws IOException {
        // 接受请求
        SocketChannel client = null;
        int count = 0;
        // 测试此键的通道是否已准备好接受新的套接字连接。
        if (selectionKey.isAcceptable()) {
            System.out.println("通道连接已可用");
            // 返回为之创建此键的通道。
            ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
            // 接受到此通道套接字的连接。
            // 此方法返回的套接字通道(如果有)将处于阻塞模式。
            client = server.accept();
            // 配置为非阻塞
            client.configureBlocking(false);
            // 注册到selector,等待连接
            client.register(selector, SelectionKey.OP_READ);
        } else if (selectionKey.isReadable()) {
            // 返回为之创建此键的通道。
            client = (SocketChannel) selectionKey.channel();
            //将缓冲区清空以备下次读取
            receivebuffer.clear();
            //读取服务器发送来的数据到缓冲区中
            count = client.read(receivebuffer);
            if (count > 0) {
                System.out.println("从客户端获取的数据:" + new String(receivebuffer.array(), 0, count));
                client.register(selector, SelectionKey.OP_WRITE);
            }
        } else if (selectionKey.isWritable()) {
            System.out.println("可向客户端写数据");
            //将缓冲区清空以备下次写入
            sendBuffer.clear();
            // 返回为之创建此键的通道。
            client = (SocketChannel) selectionKey.channel();
            sendBuffer.put("Hellow world".getBytes());
            sendBuffer.flip();
            //输出到通道
            client.write(sendBuffer);
            System.out.println("服务器端向客户端发送数据");
            client.register(selector, SelectionKey.OP_READ);
        }
    }
}

启动后,输入 http://localhost:2181/ 后,就会在控制台发现有请求和可读时间发现

AIO

即java NIO2。NIO是同步非阻塞的,提升了性能。但还是异步的。NIO2是异步的,因此也称即AIO,即Asynchronous IO。异步 IO 操作是基于事件和回调机制的。

看下例子:

 

 

 

 

 

### BIONIOAIO 的区别及应用场景 #### 区别 **BIO(Blocking I/O)** 是传统的阻塞式 I/O 模型,其特点是每次 I/O 操作都需要阻塞当前线程,直到操作完成。例如,当一个线程读取数据时,如果没有数据可读,线程将一直阻塞,直到有数据为止。这种模型适用于连接数较少且固定的场景,因为每个连接都需要一个独立的线程来处理。 **NIO(Non-blocking I/O)** 是非阻塞式 I/O 模型,它允许一个线程管理多个 I/O 操作。NIO 通过选择器(Selector)来监控多个通道(Channel)的状态,一旦某个通道有数据准备好,选择器就会通知相应的线程进行处理。这种方式使得单个线程可以处理多个连接,从而提高了系统的并发处理能力。NIO 适合处理大量并发连接的场景,如高性能的网络服务或服务器应用程序[^1]。 **AIO(Asynchronous I/O)** 是异步 I/O 模型,它是真正的异步非阻塞 I/O。在 AIO 模型中,应用程序发起 I/O 操作后,可以立即返回并继续执行其他任务,而不需要等待 I/O 操作完成。操作系统会在 I/O 操作完成后通过回调函数或事件通知的方式告知应用程序。这种方式使得应用程序能够最大限度地利用系统资源,提高文件读写的效率和系统的整体性能。AIO 适合对文件读写的实时性和并发性能要求极高的场景,如大型的多媒体文件处理系统和高性能的数据库文件系统等[^4]。 #### 应用场景 **BIO 的应用场景** BIO 适用于简单的文件读写操作,尤其是在文件较小、读写操作不频繁的情况下。例如,一些小型的工具程序,用于读取配置文件或者将简单的日志信息写入文件。在这种场景下,文件的读写操作相对独立,不需要高并发处理。然而,对于大文件或频繁的读写操作,BIO 可能会导致线程长时间阻塞,影响程序的其他功能[^4]。 **NIO 的应用场景** NIO 适用于需要高效处理大量文件 I/O 操作的场景,如大规模的数据存储系统、分布式文件系统等。在这些场景中,可能需要同时读取或写入多个文件,并且希望能够充分利用系统资源来提高文件 I/O 的效率。通过通道和缓冲区进行文件 I/O 操作,可以更灵活地控制读写的位置和大小。例如,可以使用 FileChannel 来对文件进行内存映射操作,提高文件读写的速度。此外,NIO 还可以通过选择器来监控多个文件通道的状态,实现一个线程管理多个文件 I/O 操作,提高并发处理能力。不过,在文件读写场景中,NIO 的优势可能不如在网络通信场景中那么明显,因为文件 I/O 通常更关注磁盘 I/O 的性能和文件系统的特性[^4]。 **AIO 的应用场景** AIO 适用于对文件读写的实时性和并发性能要求极高的场景,如大型的多媒体文件处理系统(同时处理多个高清视频文件的读写)、高性能的数据库文件系统等。在这些场景中,需要快速响应文件 I/O 操作完成后的事件,并且能够在不阻塞线程的情况下处理大量的文件读写任务。异步的文件 I/O 操作可以让线程在发起读写操作后立即返回,继续执行其他任务。当文件 I/O 操作完成后,通过回调函数或事件通知的方式进行后续处理,能够最大限度地利用系统资源,提高文件读写的效率和系统的整体性能。与网络通信场景类似,AIO 在文件读写场景下的编程也比较复杂,需要考虑异步回调的处理和操作系统的支持情况。而且在文件系统层面,不是所有的文件系统都能很好地支持 AIO 操作,这也限制了其在某些场景下的应用[^4]。 ### 示例代码 以下是一个简单的 AIO 服务端示例: ```java import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; public class AioServer { public static void main(String[] args) throws Exception { AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(); server.bind(new InetSocketAddress(8888)); server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() { @Override public void completed(AsynchronousSocketChannel client, Void attachment) { // 自动处理新连接 ByteBuffer buffer = ByteBuffer.allocate(1024); client.read(buffer, null, new CompletionHandler<Integer, Void>() { @Override public void completed(Integer result, Void attachment) { // 读完成回调 buffer.flip(); System.out.println("Received: " + new String(buffer.array(), 0, result)); } @Override public void failed(Throwable exc, Void attachment) { exc.printStackTrace(); } }); } @Override public void failed(Throwable exc, Void attachment) { exc.printStackTrace(); } }); // 保持主线程运行 Thread.sleep(Long.MAX_VALUE); } } ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值