Java IO 模型之 BIO、NIO、AIO 详解

目录

一. 前言

二. IO 模型

2.1. IO 模型分类

2.2. BIO、NIO、AIO 使用场景分析

2.3. NIO 和 BIO 的比较

三. BIO(同步阻塞)

3.1. BIO 编程流程

3.2. BIO 应用实例

3.3. 问题分析

四. NIO(同步非阻塞)

4.1. 基本介绍

4.1.1. 选择器(Selector)

4.1.2. 通道(Channel)

4.2. NIO 三大核心原理示意图

4.3. 缓冲区(Buffer)

4.3.1. 基本介绍

4.3.2. Buffer 类及其子类

4.3.3. ByteBuffer

4.4. 通道(Channel)

4.4.1 基本介绍

4.4.2. FileChannel 类

4.4.2.1. 应用实例1 - 本地文件写数据

4.4.2.2. 应用实例2 - 本地文件读数据

4.4.2.3. 应用实例3 - 使用一个 Buffer 完成文件读取、写入

4.4.2.4. 应用实例4 - 拷贝文件 transferFrom 方法

4.4.3. 关于 Buffer 和 Channel 的注意事项和细节

4.5. Selector(选择器)

4.5.1. 基本介绍

4.5.2. Selector 类相关方法

4.6. NIO 非阻塞网络编程原理分析图

五. AIO(异步非阻塞)

5.1. 基本介绍

5.2. AIO 的特点

5.3. AIO 的应用

六. 总结


 

一. 前言

    在 Java 编程中,IO(Input/Output)操作是非常常见的操作,它涉及到文件读写、网络通信等方面。Java 提供了各种类来支持这些操作。本文将从 IO 的基础知识讲起,逐步深入,介绍 Java IO 的各个方面。

5ce04c4e87a5415f88611829e489d57c.png

    Java IO(Input/Output)是 Java 语言中用于读写数据的 API,它提供了一系列类和接口,用于读取和写入各种类型的数据。下面是 Java IO 发展史的简要介绍:

  1. JDK 1.0(1996年) 最初的 Java IO 只支持字节流(InputStream、OutputStream)和字符流(Reader、Writer)两种,基于阻塞式 IO(BIO)模型。
  2. JDK 1.1(1997年) JDK 1.1 引入了 NIO(New IO)包,支持了缓存区(Buffer)、通道(Channel)等概念,提供了更高效的 IO 操作方式,可以实现非阻塞式 IO(NIO)模式。
  3. JDK 1.4(2002年) JDK 1.4 增加了 NIO.2 API,也称为 Java NIO with buffers,提供了更强大的文件处理功能和更高效的 IO 操作。
  4. JDK 7(2011年) JDK 7 引入了 NIO.2 的改进版——NIO.2 with Completion Ports,也称为AIO(Asynchronous IO),支持异步 IO 方式,在处理大量并发请求时具有优势。 

二. IO 模型

2.1. IO 模型分类

    Java BIO:同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。

4ed0f369926d4ccc8bcf1360e9f8c278.png

BIO 模型

    Java NIO:同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求就进行处理。

588700755aac4e45b5e1c7b1f44c3e12.png

NIO 模型

    Java AIO(NIO.2):异步非阻塞,AIO 引入异步通道的概念,采用了 Proactor 模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。

2.2. BIO、NIO、AIO 使用场景分析

    BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择,但程序简单易理解。

    NIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器间通讯等。编程比较复杂,JDK1.4 开始支持。

    AIO 方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始支持。

2.3. NIO 和 BIO 的比较

1. BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多。

2. BIO 是阻塞的,NIO 则是非阻塞的。

3. BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。

4. Buffer和Channel之间的数据流向是双向的。

三. BIO(同步阻塞)

3.1. BIO 编程流程

1. 服务器端启动一个 ServerSocket。

2. 客户端启动 Socket 对服务器进行通信,默认情况下服务器端需要对每个客户端建立一个线程与之通讯。

3. 客户端发出请求后,先咨询服务器是否有线程响应,如果没有则会等待,或者被拒绝。

4. 如果有响应,客户端线程会等待请求结束后,再继续执行。

1668efb154634da4a5a0b6dbda48cfb8.png

Blocking I/O

3.2. BIO 应用实例

package com.lm.bio;import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class BIOServer {    public static void main(String[] args) throws Exception {// 线程池机制// 思路// 1. 创建一个线程池// 2. 如果有客户端连接,就创建一个线程,与之通讯(单独写一个方法)ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();// 创建ServerSocketServerSocket serverSocket = new ServerSocket(6666);System.out.println("服务器启动了");while (true) {System.out.println("线程信息id = " + Thread.currentThread().getId() + "名字 = " + Thread.currentThread().getName());// 监听,等待客户端连接System.out.println("等待连接....");// 会阻塞在accept()final Socket socket = serverSocket.accept();System.out.println("连接到一个客户端");// 就创建一个线程,与之通讯(单独写一个方法)newCachedThreadPool.execute(new Runnable() {public void run() { // 重写// 可以和客户端通讯handler(socket);}});}}// 编写一个handler方法,和客户端通讯public static void handler(Socket socket) {try {System.out.println("线程信息id = " + Thread.currentThread().getId() + "名字 = " + Thread.currentThread().getName());byte[] bytes = new byte[1024];// 通过socket获取输入流InputStream inputStream = socket.getInputStream();// 循环的读取客户端发送的数据while (true) {System.out.println("线程信息id = " + Thread.currentThread().getId() + "名字 = " + Thread.currentThread().getName());System.out.println("read....");int read = inputStream.read(bytes);if (read != -1) {System.out.println(new String(bytes, 0, read)); // 输出客户端发送的数据} else {break;}}} catch (Exception e) {e.printStackTrace();} finally {System.out.println("关闭和client的连接");try {socket.close();} catch (Exception e) {e.printStackTrace();}}}
}

0fac2a001a5b429187b288630819e419.png

打开 telnet 客户端,输入 open 127.0.0.1 6666 命令,会一直显示“正在连接127.0.0.1...”,这时其实已经连上,此时按下 Ctrl+] 键连接成功 出现 Microsoft Telnet,就可以用命令 send 发送消息了。

3.3. 问题分析

1. 每个请求都需要创建独立的线程,与对应的客户端进行数据 Read,业务处理,数据 Write。

2. 当并发数较大时,需要创建大量线程来处理连接,系统资源占用较大。

3. 连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在 Read 操作上,造成线程资源浪费。

四. NIO(同步非阻塞)

4.1. 基本介绍

    NIO 有三大核心部分:Channel(通道)、Buffer(缓冲区)、Selector(选择器)。

    NIO 是面向缓冲区,或者面向块编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络。

    Java NIO 的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变得可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。

    通俗理解:NIO 是可以做到用一个线程来处理多个操作的。假设有 10000 个请求过来,根据实际情况,可以分配 50 或者 100 个线程来处理。不像之前的阻塞 IO 那样,非得分配 10000 个。

4.1.1. 选择器(Selector)

    选择器是 Java NIO 中的一个重要组件,它可以用于同时监控多个通道的读写事件,并在有事件发生时立即做出响应。选择器可以实现单线程监听多个通道的效果,从而提高系统吞吐量和运行效率。

4.1.2. 通道(Channel)

    通道是一个用于读写数据的对象,类似于 Java IO 中的流(Stream)。与流不同的是,通道可以进行非阻塞式的读写操作,并且可以同时进行读写操作。通道分为两种类型:FileChannel 和SocketChannel,分别用于文件和网络通信。

4.2. NIO 三大核心原理示意图

246fb335933c414c9e73c075a27ba97b.png

  1. 每个 Channel 都会对应一个 Buffer。
  2. Selector 对应一个线程,一个线程对应多个 Channel(连接)。
  3. 该图反应了有三个 Channel 注册到该 Selector //程序
  4. 程序切换到哪个 Channel 是由事件决定的,Event 就是一个重要的概念。
  5. Selector 会根据不同的事件,在各个通道上切换。
  6. Buffer 就是一个内存块,底层是有一个数组。
  7. 数据的读取写入是通过 Buffer,这个和 BIO 是不同的,BIO 中要么是输入流,或者是输出流,不能双向,但是 NIO 的 Buffer 是可以读也可以写,需要 flip 方法切换 Channel 是双向的,可以返回底层操作系统的情况,比如 Linux,底层的操作系统通道就是双向的。 

4.3. 缓冲区(Buffer)

4.3.1. 基本介绍

    缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。缓冲区对象包含了一些状态变量,例如容量(capacity)、限制(limit)、位置(position)等,用于控制数据的读写。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer,如图:

0bc46e71bffb486db4217822eaf42a9d.png

4.3.2. Buffer 类及其子类

在 NIO 中,Buffer 是一个顶层父类,它是一个抽象类,类的层级关系图:

56569820920d4e5fa147a34279dd33a2.png

Buffer 类定义了所有的缓冲区都具有的四个属性来提供关于其所包含的数据元素的信息:

3924ae35be624afba85533307d7366ae.png

Buffer 类相关方法一览:

public abstract class Buffer {// JDK1.4 时引入的 APIpublic final int capacity();	// 返回此缓冲区的容量public final int position();	// 返回此缓冲区的位置public final Buffer position(int newPosition);	// 设置此缓冲区的位置public final int limit();	// 返回此缓冲区的限制public final Buffer limit(int newLimit);	// 设置此缓冲区的限制public final Buffer mark();	// 在此缓冲区的位置设置标记public final Buffer reset();	// 将此缓冲区的位置重置为以前标记的位置public final Buffer clear();	// 清除此缓冲区,即将各个标记恢复到初始状态,但是数据并没有真正擦除public final Buffer flip();	// 反转此缓冲区public final Buffer rewind();	// 重绕此缓冲区public final int remaining();	// 返回当前位置与限制之间的元素数public final boolean hasRemaining();	// 告知在当前位置和限制之间是否有元素public abstract boolean isReadOnly();	//告知此缓冲区是否为只读缓冲区// JDK1.6 时引入的 APIpublic abstract boolean hasArray();	// 告知此缓冲区是否具有可访问的底层实现数组public abstract Object array();	// 返回此缓冲区的底层实现数组public abstract int arrayOffset();	// 返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量public abstract boolean isDirect();	// 告知此缓冲区是否为直接缓冲区
}

4.3.3. ByteBuffer

    从前面可以看出对于 Java 中的基本数据类型(boolean 除外),都有一个 Buffer 类型与之相对应,最常用的自然是 ByteBuffer 类(二进制数据),该类的主要方法如下:

public abstract class ByteBuffer {// 缓冲区创建相关 APIpublic static ByteBuffer allocateDirect(int capacity);	// 创建直接缓冲区public static ByteBuffer allocate(int capacity);	// 设置缓冲区的初始容量public static ByteBuffer wrap(byte[] array);	// 把一个数组放到缓冲区中使用public static ByteBuffer wrap(byte[] array, int offset, int length);	// 构造初始化位置 offset 和上界 length 的缓冲区// 缓冲区存取相关 APIpublic abstract byte get();	// 从当前位置position上get,get之后,position会自动+1public abstract byte get(int index);	// 从绝对位置getpublic abstract ByteBuffer put(byte b);	// 从当前位置上put,put之后,position会自动+1public abstract ByteBuffer put(int index, byte b);	// 从绝对位置上put
}

4.4. 通道(Channel)

4.4.1 基本介绍

NIO 的通道类似于流,但有些区别如下:

  1. 通道可以同时进行读写,而流只能读或者只能写;
  2. 通道可以实现异步读写数据;
  3. 通道可以从缓冲读数据,也可以写数据到缓冲。

    BIO 中的 Stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)是双向的,可以读操作,也可以写操作。Channel 在 NIO 中是一个接口 public interface Channel extends Closeable {}。

    常用的 Channel 类有:FileChannel、DatagramChannel、ServerSocketChannel 和 SocketChannel。ServerSocketChanne 类似 ServerSocket、SocketChannel 类似 Socket
FileChannel 用于文件的数据读写,DatagramChannel 用于 UDP 的数据读写,ServerSocketChannel 和 SocketChannel 用于 TCP 的数据读写。

08723db9485c41efa026423c03ce84ce.png

4.4.2. FileChannel 类

FileChannel 主要用来对本地文件进行 IO 操作,常见的方法有:

public int read(ByteBuffer dst);	// 从通道读取数据并放到缓冲区中
public int write(ByteBuffer src);	//把缓冲区的数据写到通道中
public long transferFrom(ReadableByteChannel src, long position, long count);	// 从目标通道中复制数据到当前通道
public long transferTo(long position, long count, WritableByteChannel target);	// 把数据从当前通道复制给目标通道

4.4.2.1. 应用实例1 - 本地文件写数据

使用前面学习后的 ByteBuffer(缓冲)和 FileChannel(通道),将 “hello,流华追梦” 写入到 file01.txt 中:

package com.lm.nio;import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class NIOFileChannel01 {public static void main(String[] args) throws Exception {String str = "hello,流华追梦";// 创建一个输出流 -> channelFileOutputStream fileOutputStream = new FileOutputStream("d:\\file01.txt");// 通过 fileOutputStream 获取对应的 FileChannel// 这个 fileChannel 真实类型是 FileChannelImplFileChannel fileChannel = fileOutputStream.getChannel();// 创建一个缓冲区 ByteBufferByteBuffer byteBuffer = ByteBuffer.allocate(1024);// 将 str 放入 byteBufferbyteBuffer.put(str.getBytes());// 对 byteBuffer 进行 flipbyteBuffer.flip();// 将 byteBuffer 数据写入到 fileChannelfileChannel.write(byteBuffer);fileOutputStream.close();}
}

4.4.2.2. 应用实例2 - 本地文件读数据

使用前面学习后的 ByteBuffer(缓冲)和 FileChannel(通道),将 file01.txt 中的数据读入到程序,并显示在控制台屏幕:

package com.lm.nio;import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class NIOFileChannel02 {public static void main(String[] args) throws Exception {// 创建文件的输入流File file = new File("d:\\file01.txt");FileInputStream fileInputStream = new FileInputStream(file);// 通过 fileInputStream 获取对应的 FileChannel -> 实际类型 FileChannelImplFileChannel fileChannel = fileInputStream.getChannel();// 创建缓冲区ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length());// 将通道的数据读入到 BufferfileChannel.read(byteBuffer);// 将 byteBuffer 的字节数据转成 StringSystem.out.println(new String(byteBuffer.array()));fileInputStream.close();}
}

4.4.2.3. 应用实例3 - 使用一个 Buffer 完成文件读取、写入

使用 FileChannel(通道)和方法 read、write,完成文件的拷贝:

fcb5cfac545f429897dcd2cd7813d573.png

package com.lm.nio;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class NIOFileChannel03 {public static void main(String[] args) throws Exception {FileInputStream fileInputStream = new FileInputStream("1.txt");FileChannel fileChannel01 = fileInputStream.getChannel();FileOutputStream fileOutputStream = new FileOutputStream("2.txt");FileChannel fileChannel02 = fileOutputStream.getChannel();ByteBuffer byteBuffer = ByteBuffer.allocate(512);while (true) { // 循环读取// 这里有一个重要的操作,一定不要忘了/*public final Buffer clear() {position = 0;limit = capacity;mark = -1;return this;}*/byteBuffer.clear(); // 清空 bufferint read = fileChannel01.read(byteBuffer);System.out.println("read = " + read);if (read == -1) { // 表示读完break;}// 将 buffer 中的数据写入到 fileChannel02--2.txtbyteBuffer.flip();fileChannel02.write(byteBuffer);}// 关闭相关的流fileInputStream.close();fileOutputStream.close();}
}

4.4.2.4. 应用实例4 - 拷贝文件 transferFrom 方法

使用 FileChannel(通道)和方法 transferFrom,完成文件的拷贝:

package com.lm.nio;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;public class NIOFileChannel04 {public static void main(String[] args) throws Exception {// 创建相关流FileInputStream fileInputStream = new FileInputStream("d:\\a.jpg");FileOutputStream fileOutputStream = new FileOutputStream("d:\\a2.jpg");// 获取各个流对应的 FileChannelFileChannel sourceCh = fileInputStream.getChannel();FileChannel destCh = fileOutputStream.getChannel();// 使用 transferForm 完成拷贝destCh.transferFrom(sourceCh, 0, sourceCh.size());// 关闭相关通道和流sourceCh.close();destCh.close();fileInputStream.close();fileOutputStream.close();}
}

4.4.3. 关于 Buffer 和 Channel 的注意事项和细节

ByteBuffer 支持类型化的 put 和 get,put 放入的是什么数据类型,get 就应该使用相应的数据类型来取出,否则可能有 BufferUnderflowException 异常:

package com.lm.nio;import java.nio.ByteBuffer;public class NIOByteBufferPutGet {public static void main(String[] args) {// 创建一个 BufferByteBuffer buffer = ByteBuffer.allocate(64);// 类型化方式放入数据buffer.putInt(100);buffer.putLong(9);buffer.putChar('尚');buffer.putShort((short) 4);// 取出buffer.flip();System.out.println();System.out.println(buffer.getInt());System.out.println(buffer.getLong());System.out.println(buffer.getChar());System.out.println(buffer.getShort());}
}

可以将一个普通 Buffer 转成只读 Buffer:

package com.lm.nio;import java.nio.ByteBuffer;public class ReadOnlyBuffer {public static void main(String[] args) {// 创建一个 bufferByteBuffer buffer = ByteBuffer.allocate(64);for (int i = 0; i < 64; i++) {buffer.put((byte) i);}// 读取buffer.flip();// 得到一个只读的 BufferByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();System.out.println(readOnlyBuffer.getClass());// 读取while (readOnlyBuffer.hasRemaining()) {System.out.println(readOnlyBuffer.get());}readOnlyBuffer.put((byte) 100); //ReadOnlyBufferException}
}

NIO 还提供了 MappedByteBuffer,可以让文件直接在内存(堆外的内存)中进行修改,而如何同步到文件由 NIO 来完成:

package com.lm.nio;import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;/*** 说明 1.MappedByteBuffer 可让文件直接在内存(堆外内存)修改,操作系统不需要拷贝一次*/
public class MappedByteBufferTest {public static void main(String[] args) throws Exception {RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");// 获取对应的通道FileChannel channel = randomAccessFile.getChannel();/*** 参数 1:FileChannel.MapMode.READ_WRITE 使用的读写模式* 参数 2:0:可以直接修改的起始位置* 参数 3:5: 是映射到内存的大小(不是索引位置),即将 1.txt 的多少个字节映射到内存* 可以直接修改的范围就是 0-5* 实际类型 DirectByteBuffer*/MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);mappedByteBuffer.put(0, (byte) 'H');mappedByteBuffer.put(3, (byte) '9');mappedByteBuffer.put(5, (byte) 'Y');//IndexOutOfBoundsExceptionrandomAccessFile.close();System.out.println("修改成功~~");}
}

前面我们讲的读写操作,都是通过一个 Buffer 完成的,NIO 还支持通过多个 Buffer(即 Buffer 数组)完成读写操作,即 Scattering 和 Gathering:

package com.lm.nio;import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;/*** Scattering:将数据写入到 buffer 时,可以采用 buffer 数组,依次写入 [分散]* Gathering:从 buffer 读取数据时,可以采用 buffer 数组,依次读*/
public class ScatteringAndGatheringTest {public static void main(String[] args) throws Exception {        // 使用 ServerSocketChannel 和 SocketChannel 网络ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);// 绑定端口到 socket,并启动serverSocketChannel.socket().bind(inetSocketAddress);// 创建 buffer 数组ByteBuffer[] byteBuffers = new ByteBuffer[2];byteBuffers[0] = ByteBuffer.allocate(5);byteBuffers[1] = ByteBuffer.allocate(3);// 等客户端连接 (telnet)SocketChannel socketChannel = serverSocketChannel.accept();int messageLength = 8; // 假定从客户端接收 8 个字节// 循环的读取while (true) {int byteRead = 0;while (byteRead < messageLength) {long l = socketChannel.read(byteBuffers);byteRead += l; // 累计读取的字节数System.out.println("byteRead = " + byteRead);// 使用流打印,看看当前的这个 buffer 的 position 和 limitArrays.asList(byteBuffers).stream().map(buffer -> "position = " + buffer.position() + ", limit = " + buffer.limit()).forEach(System.out::println);}// 将所有的 buffer 进行 flipArrays.asList(byteBuffers).forEach(buffer -> buffer.flip());// 将数据读出显示到客户端long byteWirte = 0;while (byteWirte < messageLength) {long l = socketChannel.write(byteBuffers);//byteWirte += l;}// 将所有的buffer进行clearArrays.asList(byteBuffers).forEach(buffer -> {buffer.clear();});System.out.println("byteRead = " + byteRead + ", byteWrite = " + byteWirte + ", messagelength = " + messageLength);}}
}

4.5. Selector(选择器)

4.5.1. 基本介绍

Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector(选择器)。

Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个 Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。

只有在连接/通道真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。

避免了多线程之间的上下文切换导致的开销。

8acf9b6af1824dc3a3a9e44349f0faaa.png

Nonblocking I/O

4.5.2. Selector 类相关方法

Selector 在是一个抽象类,常用方法说明如下:

ba4d0e665bad4c9d8c3a7dad57cf802d.png

4.6. NIO 非阻塞网络编程原理分析图

c77a2090f0ec486b9761670e119e80d4.jpeg

对上图的说明:

  1. 当客户端连接时,会通过 ServerSocketChannel 得到 SocketChannel。
  2. Selector 进行监听 select 方法,返回有事件发生的通道的个数。
  3. 将 socketChannel 注册到 Selector 上,register(Selector sel, int ops),一个 Selector 上可以注册多个 SocketChannel。
  4. 注册后返回一个 SelectionKey,会和该 Selector 关联(集合)。
  5. 进一步得到各个 SelectionKey(有事件发生)。
  6. 在通过 SelectionKey 反向获取 SocketChannel,方法 channel()。
  7. 可以通过得到的 channel,完成业务处理。

五. AIO(异步非阻塞)

5.1. 基本介绍

    JDK7 引入了 Asynchronous I/O,即 AIO。在进行 I/O 编程中,常用到两种模式:Reactor 和 Proactor。Java 的 NIO 就是 Reactor,当有事件触发时,服务器端得到通知,进行相应的处理。

    AIO 即 NIO2.0,叫做异步不阻塞的 IO。AIO 引入异步通道的概念,采用了 Proactor 模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。

    AIO 使用了三个核心组件:AsynchronousChannel、CompletionHandler 和 AsynchronousServerSocketChannel。其中,AsynchronousChannel 是读/写数据的通道,CompletionHandler 是 I/O 操作完成时的回调方法,AsynchronousServerSocketChannel 是异步服务器端套接字通道,用于监听客户端的连接请求。

5.2. AIO 的特点

1. 高并发性:Java AIO 采用异步 IO 方式进行数据读写操作,可以实现高并发处理能力。

2. 高吞吐量:Java AIO 支持异步读写操作,可以同时处理多个请求,从而提高了数据读写的效率和吞吐量。

3. 高可靠性:由于Java AIO 采用异步 IO 方式进行数据读写操作,可以避免线程阻塞等待 I/O 操作完成的情况,从而提高程序的可靠性。

4. 简单易用:Java AIO 提供了简单易用的 API,不需要编写复杂的代码就可以实现异步 IO 操作。

5.3. AIO 的应用

    Java AIO 适用于需要大量并发连接,但每个连接却很少有数据交互的场景,例如基于消息的应用程序、远程过程调用(RPC)等。在这些应用场景中,AIO 可以大幅度提高程序的性能和并发处理能力,从而满足用户对高吞吐量和低延迟的要求。

    另外,Java AIO 也可以用于开发高性能的网络服务器,例如聊天室服务器、在线游戏服务器等。由于 AIO 支持异步读取输入流和输出流,因此可以同时处理多个客户端请求,有效地提高了服务器的并发处理能力。

六. 总结

BIO、NIO、AIO 对比表:

 BIONIOAIO
IO模型同步阻塞同步非阻塞(多路复用)异步非阻塞
编程难度简单复杂复杂
可靠性
吞吐量

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/223340.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Dubbo 3.x源码(13)—Dubbo服务发布导出源码(2)

基于Dubbo 3.1&#xff0c;详细介绍了Dubbo服务的发布与引用的源码。 此前我们学习了Dubbo 3.x源码(12)—Dubbo服务发布导出源码(1)&#xff0c;也就是Dubbo服务发布导出的入口源码&#xff0c;现在我们继续学习&#xff0c;服务导出的核心方法doExportUrls的源码。 Dubbo 3.x…

一文读懂Java中的设计模式——单例模式!默认情况下,Spring的Bean就是单例的

单例模式概念 单例模式确保某一个类只有一个实例&#xff0c;而且自行实例化并向整个系统提供这个实例。单例模式只应在有真正的“单一实例”的需求时才可使用。总结起来单例模式有三个优点&#xff1a; 全局访问共享资源&#xff1a;当需要在应用程序的多个地方共享和使用相…

跑代码中遇到的错误合集(持续更新)

1.TypeError: dropout(): argument ‘input‘ (position 1) must be Tensor, not str 原因&#xff1a;dropout函数接收到的参数是一个字典类型(需手动设置其不要返回字典类型) 解决步骤: 1.根据代码定位到dropout函数 2.定位到函数中的参数 3.对给dropout函数参数赋值的函数的…

[渗透测试学习] Sau - HackTheBox

首先是信息搜集&#xff0c;nmap扫一下 nmap -sV -sC -p- -v 10.10.11.224 发现存在两个端口&#xff0c;55555端口有http服务&#xff0c;访问一下 获得线索request-baskets版本为1.2.1&#xff0c;搜索发现存在漏洞 那么我们试试构造ssrf&#xff0c;create的时候bp抓包 构…

定时器TIM HAL库+cubeMX(上)

定时器时钟源APB1 36MHz 一.基本定时器 1.基本框图 2.溢出时间计算 3.配置定时器步骤 TIM_HandleTypeDef g_timx_handle;/* 定时器中断初始化函数 */ void btim_timx_int_init(uint16_t arr, uint16_t psc) {g_timx_handle.Instance TIM6;g_timx_handle.Init.Prescaler p…

【数据安全】金融行业数据安全保障措施汇总

数字化的今天&#xff0c;数据的价值不可估量&#xff0c;尤其是金融行业&#xff0c;数据不仅代表着企业的核心资产&#xff0c;还涉及到客户的隐私和信任。因此对于金融行业而言&#xff0c;保障数据安全至关重要。下面我们就来一起讨论为什么金融行业要保障数据安全&#xf…

Idea执行bat使用maven打包springboot项目成docker镜像并push到Harbor

如果执行以下命令失败&#xff0c;先把mvn的-q参数去掉&#xff0c;让错误输出到控制台。 《idea配置优化、Maven配置镜像、并行构建加速打包、解决maven打包时偶尔几个文件没权限的问题》下面的使用company-repo私有仓库和阿里云镜像仓库同时使用的配置参考。 bat echo off …

要实现长页面滑动到指定位置触发动画效果(亲测有效)

1.添加触发动画的元素&#xff1a;在你的 HTML 文件中&#xff0c;将需要触发动画的元素添加相应的类名<div class"animation"> <p>安全工矿 智能工矿 安全工矿 智能工矿</p> </div> 给一个 <div> 元素添加 .animation…

JVM 详解(JVM组成部分、双亲委派机制、垃圾回收算法、回收器、回收类型、了解调优思路)

目录 JVM 详解&#xff08;JVM组成部分、双亲委派机制、垃圾回收算法、回收器、回收类型、了解调优思路&#xff09;1、概念&#xff1a;什么是 JVM ?JVM 的作用&#xff1f; 2、JVM 的主要组成部分&#xff1f;类加载器&#xff08;Class Loader&#xff09;&#xff1a;简单…

02基于matlab的卡尔曼滤波

基于matlab的卡尔曼滤波&#xff0c;可更改状态转移方程&#xff0c;控制输入&#xff0c;观测方程&#xff0c;设置生成的信号的噪声标准差&#xff0c;设置状态转移方差Q和观测方差R等参数&#xff0c;程序已调通&#xff0c;需要直接拍下。

Vue学习计划-Vue2--VueCLi(五)全局事件总线、消息订阅与发布(pubsub)

抛出问题:我们多级组件&#xff0c;或者任意不想关的子组件如何传递数据呢&#xff1f; 1. 全局事件总线&#xff08;$bus&#xff09; 一种组件间通信的方式&#xff0c;适用于任意组件间通信 全局事件总线示意图&#xff1a; 安装全局事件总线&#xff1a; new Vue({..…

数据结构和算法-图的基本概念及邻接矩阵法和邻接表法和十字链表法和链表链表法

文章目录 图的概念总览图的定义图逻辑结构的应用无向图和有向图简单图和多重图顶点的度&#xff0c;入读&#xff0c;出度顶点-顶点的关系描述连通图&#xff0c;强连通图研究图的局部-子图无向图有向图 连通分量强连通分量生成树生成森林边的权&#xff0c;带权图/网几种特殊形…

虚幻学习笔记15—C++和UI(一)

一、前言 在C可以直接创建按钮、滚轮等UI&#xff0c;并且可以直接绑定并处理响应事件。在创建C代码后还是需要通过蓝图来显示到应用中&#xff0c;总体来说还是不如直接用蓝图来的方便。 本文使用的虚幻引擎为5.2.1。 二、实现 2.1、创建UUserWidgetl类型的C类 声明两个按钮…

uniCloud(一) 新建项目、初始化服务空间、云对象访问测试

一、新建一个带有unicloud 二、创建一个服务空间 1. 右键uniCloud&#xff0c;关联云服务空间 我当前没有服务空间&#xff0c;需要新建一个服务空间&#xff0c;之后将其关联。初始化服务空间需要的时间有点长 服务空间初始化成功后&#xff0c;刷新HBuilder&#xff0c;勾选…

Linux系统下CPU性能问题分析案例

&#xff08;上&#xff09; 本文涉及案例来自于学习极客时间专栏《Linux性能优化实战》精心整理而来&#xff0c;案例总结不到位的请各位多多指正。 某个应用的CPU使用率居然达到100%&#xff0c;我该怎么办&#xff1f; 分析过程 使用观察系统CPU使用情况&#xff08;并按下…

03. 医院设置_后端

1、Swagger2 测试工具 编写和维护接口文档是每个程序员的职责&#xff0c;根据Swagger2可以快速帮助我们编写最新的API接口文档&#xff0c;再也不用担心开会前仍忙于整理各种资料了&#xff0c;间接提升了团队开发的沟通效率。 swagger通过注解表明该接口会生成文档&#xf…

vuepress-----25、右侧目录

# 25、vuepress 右侧目录 https://github.com/xuek9900/vuepress-plugin-right-anchor vuepress-plugin-right-anchor English &#xff5c;中文 在用 Vuepress 2.x 编写的文档页面右侧添加 锚点导航栏 # 版本 2.x.x -> Vuepress 2.x -> npm next -> master 分支0…

PS扣印章

1 印章区域图片 2 3 吸取印章上的颜色&#xff0c;调节容差&#xff0c;尽量小一点&#xff0c;过大会将背景也进来 4 CtrlJ 把选区复制出来&#xff0c;这个印章图层比较淡&#xff0c;可以通过多复制几个叠加或通过叠加模式来调节。 5 对几个图层选中后CtrlE合并图层 6 选…

IT圈茶余饭后的“鄙视链”

哈哈&#xff0c;IT圈的鄙视链&#xff0c;简直就是一出情感大戏&#xff01;这个圈子里的人们总是忍不住要互相比较&#xff0c;互相鄙视&#xff0c;仿佛这是一场刺激的游戏&#xff0c;每个人都想要站在鄙视链的最顶端&#xff0c;成为那个最牛逼的存在。 首先&#xff0c;…

深度学习第5天:GAN生成对抗网络

☁️主页 Nowl &#x1f525;专栏 《深度学习》 &#x1f4d1;君子坐而论道&#xff0c;少年起而行之 ​​ 文章目录 一、GAN1.基本思想2.用途3.模型架构 二、具体任务与代码1.任务介绍2.导入库函数3.生成器与判别器4.预处理5.模型训练6.图片生成7.不同训练轮次的结果对比 一…