Java NIO

server/2024/10/19 3:26:22/

1. IO分类概述

1.1 阻塞与非阻塞

        阻塞(Blocking)和非阻塞(Nonblocking)是在计算机编程中用于描述I/O操作的两个重要概念。阻塞与非阻塞描述的是线程在访问某个资源时,在该资源没有准备就绪期间的处理方式。

        1、阻塞:阻塞是指在进行I/O操作时,当前线程会被挂起,等待数据的就绪或操作的完成。

  • 在阻塞状态下,线程会一直等待,直到条件满足或超时才会继续执行
  • 阻塞式I/O操作会导致调用线程无法执行其他任务,直到I/O操作完
  • 例如,在读取文件时,如果没有数据可用,线程会一直等待直到有数据可读

        2、非阻塞:非阻塞是指进行I/O操作时,当前线程不会被挂起,而是立即返回并继续执行其他任务。

  • 在非阻塞状态下,即使I/O操作无法立即完成,线程也可以继续执行其他操作,而不需要一直等待
  • 非阻塞式I/O操作通常会返回一个状态或错误码,指示操作是否完成或需要进一步处理
  • 应用程序可以通过不断轮询状态来判断是否可以进行下一步操作

1.2 同步与异步

        同步(synchronous)与异步(asynchronous)描述的是线程在发出请求后,是否等待结果。

        1、同步(Synchronous):同步是指程序按照顺序执行,并等待某个操作完成后再继续执行下一个操作。

  • 在同步操作中,程序主动发起请求并等待结果返回,直到结果返回后才能继续执行后续操作
  • 同步操作通常是阻塞的,即在等待结果时当前线程会被挂起,无法执行其他任务
  • 典型的同步操作包括函数调用、阻塞式I/O操作等

        2、异步(Asynchronous):异步是指程序在发起某个操作后,不需要立即等待操作完成,而是继续执行后续的操作。

  • 在异步操作中,程序不会阻塞等待结果的返回,而是通过回调、轮询或事件通知等机制来获取结果或处理完成的事件
  • 异步操作允许程序并发执行多个任务,提高了系统的并发性和响应性。典型的异步操作包括异步函数调用、非阻塞式I/O操作、异步任务等

1.3 IO的分类

        当涉及I/O操作时,可以根据是否阻塞和是否异步的角度将其分为以下四类:

        1、阻塞式I/O(Blocking I/O)

  • 特点:在进行I/O操作时,当前线程会被阻塞,直到数据就绪或操作完成
  • 工作原理:当进行I/O操作时,线程会等待直到数据准备好或操作完成,在等待期间,线程无法执行其他任务

        2、非阻塞式I/O(Non-Blocking I/O)

  • 特点:进行I/O操作时,当前线程不会被阻塞,立即返回并继续执行其他任务
  • 工作原理:当进行I/O操作时,如果数据尚未准备好或操作无法立即完成,操作会立即返回一个状态指示暂时不可用

        3、I/O多路复用(I/O Multiplexing)

  • 特点:允许一个线程同时监视多个I/O通道的就绪状态,提高了系统的并发性能
  • 工作原理:通过操作系统提供的I/O复用机制,将多个I/O通道注册到一个选择器上,然后使用选择器进行轮询以确定哪些通道就绪

        4、异步I/O(Asynchronous I/O)

  • 特点:在进行I/O操作时,不需要立即等待操作完成,可以继续执行其他任务,通过回调或事件机制来处理操作结果
  • 工作原理:应用程序提交I/O请求后立即返回,并使用回调、轮询或事件通知等方式来处理操作完成的通知

        这四种I/O模型各有优劣,并适用于不同的应用场景:

  • 阻塞式I/O适用于简单的同步操作
  • 非阻塞式I/O适用于需要处理多个连接的场景
  • I/O多路复用适用于需要监视多个连接的场景
  • 异步I/O适用于需要高性能和扩展性的场景

        根据具体的应用需求和系统特点,可以选择合适的I/O模型来实现高效的数据传输和处理。

2. Java NIO

2.1 Java NIO概述

        在Java编程中,经常听到BIO,NIO,AIO等名词,这些名词一般指的是Java语言为实现不同类型的I/O操作所提供的API。

        1、Java IO:Java IO是JDK 1.0版本其自带的用于读取和写入数据的API。因其同步、阻塞的特征,被归类为同步阻塞式IO(Blocking IO),即Java BIO。

        2、Java NIO(New IO):是JDK 1.4开始提供的同步非阻塞式的IO操作API。因其同步、非阻塞的特征,开发者一般将Java NIO理解为Java Non-Blocking IO。

        3、Java NIO 2.0:是JDK 1.7开始提供的异步非阻塞式的IO操作API,因其是在NIO的基础上进行了改进,称为NIO 2.0。因其异步、非阻塞的特征,开发者一般将Java NIO 2.0称为Java AIO。

        下面以一个Web服务器的例子介绍Java BIO和Java NIO在实际使用中的差别。

        基于BIO的场景:

        基于NIO的场景:

2.2 Java BIO模型

        Java BIO和NIO采用了2种不同的模型。BIO使用了面向流(Stream Oriented)的模型。流(Stream)可以理解为从源节点到目标节点的数据通道,传输的数据像水流一样从源节点流向目标节点。

        面向流的特点:

  • 单向的:一个流中的数据仅能从一个方向流向另一个方向
  • 面向字节的:程序每次可以从流中读取1到多个字节,或写入1到多个字节
  • 无缓冲的:从流中读取数据后,流中的数据消失
  • 顺序访问:仅能按顺序逐个访问流中的数据,不能在流中的数据中前后移动

2.3 Java NIO模型

        Java NIO使用了面向缓冲区(Buffer Oriented)的,基于通道(Channel Based)的模型。模型的不同使得BIO和NIO在功能上和操作上有着不同的特点。

        在面向缓冲的模型中,数据通过数据通道(Channel)被读入/写入到一个缓冲区(Buffer)中,然后从中进行处理。

        可以使用一个生活中的例子来理解:现在需要从房间A搬运一些纸质文件到房间B,房间A和房间B之间通过一个走廊相连。搬运文件时,先将文件放到一个文件箱中,再搬着文件箱从A房间移动到B房间。

        在这个例子中,房间A和房间B分别是数据传输的起点和终点。起点和终点之间的走廊是Channel,临时存放纸质文件的文件箱是Buffer。

        面向缓冲的特点:

  • 双向的:通道可以用于读或写,也可以同时用于读写
  • 面向字节块:程序每次可以从缓冲区中获取一组字节数据
  • 缓冲的:缓冲中的数据可以被多次访问
  • 任意访问:允许在缓冲中的数据中前后移动

2.4 两种模型的对比

        Java BIO下的执行流程:

        Java NIO下的执行流程:

3. Java NIO API

3.1 Buffer

        Buffer(缓冲区)是Java NIO中提供的用于存储数据的容器。Buffer底层依靠数组来存储数据,并提供了对数据的结构化访问以及维护读写位置等信息的功能。

        Buffer只能用于存储基本类型的数据。在NIO中,针对八种基本类型提供了7个实现类。考虑到实际过程中,数据是以字节的形式来存储和传输,所以更多的使用的是ByteBuffer。

        Buffer是一个抽象类:

        其中定义了4个关于底层数组信息的核心属性:

  • capacity:容量位,用于标记该缓冲区的容量,缓冲区创建好之后不可变
  • position:操作位,用于指向要操作的位置,实际意义类似于数组中的下标,在缓冲区刚创建的时候指向0
  • limit:限制位,用于限制操作位position所能达到的最大位置。在缓冲区刚创建的时候指向容量位
  • mark:标记位,用于进行标记,在缓冲区刚创建的时候指向-1,默认不启用

        Buffer初始时的状态:

        示例代码:

        输出结果:

        Buffer写入部分数据后的状态:

        示例代码:

        输出结果:

        Buffer反转后的状态:

        示例代码:

        输出结果:

java">import java.nio.ByteBuffer;public class BufferDemo {public static void main(String[] args) {//创建BufferByteBuffer buf = ByteBuffer.allocate(10);printBufferState(buf);// 写入数据到ByteBufferString message = "Hello NIO";buf.put(message.getBytes());System.out.println("Before flip():");printBufferState(buf);// 反转缓冲区buf.flip();// 打印切换到读模式后的状态System.out.println("After flip():");printBufferState(buf);// 读取数据byte[] data = new byte[buf.limit()];buf.get(data);// 打印读取到的数据System.out.println("Read data: " + new String(data));}public static void printBufferState(ByteBuffer buf){//输出3个变量的值System.out.println("position="+buf.position()+",limit="+buf.limit()+",capacity="+buf.capacity());}
}

3.2 Channel

        Channel(通道)是Java NIO中提供的用于传输数据的工具,代表了源节点和目标节点之间的数据通道。Channel与Stream相似,但是略有不同:

  • Channel是双向的
  • Channel默认是阻塞的,可以设置为非阻塞
  • Channel是面向缓冲区操作的

        Java NIO中常用的Channel实现类如下:

        其中:

  • FileChannel:从文件读取数据和向文件读取数据
  • DatagramChannel:可以通过UDP协议在网络上读写数据
  • SocketChannel:可以通过TCP协议在网络上读写数据
  • ServerSocketChannel:允许您侦听传入的 TCP 连接,就像 Web 服务器一样。 对于每个传入连接,都会创建一个 SocketChannel
java">import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class FileChannelDemo {public static void main(String[] args) throws Exception {readDemo();writeDemo();}public static void readDemo() throws Exception {RandomAccessFile aFile = new RandomAccessFile("data/hello.txt", "rw");FileChannel inChannel = aFile.getChannel();ByteBuffer buf = ByteBuffer.allocate(10);int bytesRead = inChannel.read(buf);while (bytesRead != -1) {System.out.println("\n====>Read " + bytesRead);buf.flip();while(buf.hasRemaining()){System.out.print((char) buf.get()+" ");}buf.clear();bytesRead = inChannel.read(buf);}aFile.close();}public static void writeDemo() throws Exception {RandomAccessFile aFile = new RandomAccessFile("data/hello2.txt", "rw");FileChannel channel = aFile.getChannel();ByteBuffer buf = ByteBuffer.wrap("Hello Channel!".getBytes());int len=channel.write(buf);System.out.println("len="+len);aFile.close();}
} 

3.3 Selector

        Selector 是 Java NIO 提供的一个组件,它可以检查一个或多个Channel 实例,并确定哪些通道准备好用于读、写等操作。 通过这种方式,单个线程可以管理多个通道,从而管理多个网络连接。

        Selector是基于事件驱动的,供提供了4类事件:connect、accept、read和write。

        这四类事件定义在SelectionKey中:

  • SelectionKey.OP_CONNECT
  • SelectionKey.OP_ACCEPT
  • SelectionKey.OP_READ
  • SelectionKey.OP_WRITE

        想要使用Selector来管理Channel,需要先向Selector注册该Channel实例,可以通过SelectableChannel.register()方法实现。

java">// 将channel设置为非阻塞模式
channel.configureBlocking(false);
// 将通道注册到选择器,并指定关注事件为读事件
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

        需要注意,Channel 必须处于非阻塞模式才能与 Selector 一起使用。 也就是说,不能将 FileChannel 与 Selector 一起使用,因为 FileChannel 无法切换到非阻塞模式。SocketChannel是可以与Selector搭配使用的。

        创建一个ServerSocketChannel监听8080端口,并使用Selector来处理客户端的连接和数据读取。同时,创建了多个客户端线程,模拟并发访问。每个客户端线程会连接到服务器,并发送数据,然后接收服务器的响应。

        Server端程序代码:

java">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 Server {public static void main(String[] args) throws IOException {// 创建SelectorSelector selector = Selector.open();// 创建ServerSocketChannel并绑定端口ServerSocketChannel serverChannel = ServerSocketChannel.open();serverChannel.bind(new InetSocketAddress(8080));serverChannel.configureBlocking(false);// 将ServerSocketChannel注册到Selector上,并指定感兴趣的事件为接收连接事件serverChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("Server started.");while (true) {// Selector进行事件轮询selector.select();// 获取触发的事件集合Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectedKeys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove();if (key.isAcceptable()) {// 接收连接事件handleAccept(key);} else if (key.isReadable()) {// 可读事件handleRead(key);}}}}private static void handleAccept(SelectionKey key) throws IOException {ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();SocketChannel clientChannel = serverChannel.accept();clientChannel.configureBlocking(false);clientChannel.register(key.selector(), SelectionKey.OP_READ);System.out.println("Accepted new connection from: " + clientChannel.getRemoteAddress());}private static void handleRead(SelectionKey key) throws IOException {SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = clientChannel.read(buffer);if (bytesRead == -1) {System.out.println("Connection closed by client: " + clientChannel.getRemoteAddress());// 客户端关闭连接clientChannel.close();} else if (bytesRead > 0) {// 处理接收到的数据buffer.flip();byte[] data = new byte[buffer.limit()];buffer.get(data);System.out.println("Received data from " + clientChannel.getRemoteAddress() + ": " + new String(data));// 回写响应数据String response = "Response from server";ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes());clientChannel.write(responseBuffer);}}
}

        Client端程序代码:

java">import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;public class Client {public static void main(String[] args) {for (int i = 0; i < 5; i++) {Thread clientThread = new Thread(new ClientRunnable());clientThread.start();}}static class ClientRunnable implements Runnable {@Overridepublic void run() {try {// 创建客户端SocketChannel并连接到服务器SocketChannel clientChannel = SocketChannel.open();clientChannel.configureBlocking(false);clientChannel.connect(new InetSocketAddress("localhost", 8080));// 等待连接完成while (!clientChannel.finishConnect()) {Thread.sleep(100);}// 发送数据到服务器String message = "Hello from client";ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());clientChannel.write(buffer);// 接收服务器响应ByteBuffer responseBuffer = ByteBuffer.allocate(1024);while (clientChannel.read(responseBuffer) <= 0) {// 等待服务器响应Thread.sleep(100);}responseBuffer.flip();byte[] responseData = new byte[responseBuffer.limit()];responseBuffer.get(responseData);System.out.println( clientChannel.getLocalAddress()+"=>Received response from server: " + new String(responseData));// 关闭客户端连接clientChannel.close();} catch (IOException | InterruptedException e) {e.printStackTrace();}}}
}

4. 总结

        1、阻塞(Blocking)和非阻塞(Nonblocking)描述的是线程在访问某个资源时,在该资源没有准备就绪期间的处理方式

  • 阻塞:阻塞是指在进行I/O操作时,当前线程会被挂起,等待数据的就绪或操作的完成
  • 非阻塞:非阻塞是指进行I/O操作时,当前线程不会被挂起,而是立即返回并继续执行其他任务

        2、同步(synchronous)与异步(asynchronous)描述的是线程在发出请求后,是否等待结果

  • 同步是指程序按照顺序执行,并等待某个操作完成后再继续执行下一个操作
  • 在异步操作中,程序不会阻塞等待结果的返回,而是通过回调、轮询或事件通知等机制来获取结果或处理完成的事件

        3、当涉及I/O操作时,可以根据是否阻塞和是否异步的角度将其分为以下四类

  • 阻塞式I/O(Blocking I/O)
  • 非阻塞式I/O(Non-Blocking I/O)
  • I/O多路复用(I/O Multiplexing)
  • 异步I/O(Asynchronous I/O)

        4、在Java编程中提到的BIO、NIO和AIO,一般指的是Java语言为实现不同类型的I/O操作所提供的API

  • Java IO:Java IO是JDK 1.0版本其自带的用于读取和写入数据的API,因其同步、阻塞的特征,被归类为同步阻塞式IO(Blocking IO),即Java BIO
  • Java NIO(New IO):是JDK 1.4开始提供的同步非阻塞式的IO操作API,因其同步、非阻塞的特征,开发者一般将Java NIO理解为Java Non-Blocking IO
  • Java NIO 2.0:是JDK 1.7开始提供的异步非阻塞式的IO操作API,因其是在NIO的基础上进行了改进,称为NIO 2.0;因其异步、非阻塞的特征,开发者一般将Java NIO 2.0称为Java AIO

        5、Java BIO和NIO采用了2种不同的模型

  • BIO使用了面向流(Stream Oriented)的模型
  • Java NIO使用了面向缓冲区(Buffer Oriented)的,基于通道(Channel Based)的模型

        6、Java NIO编程中的核心API包括Buffer、Channel和Selector

  • Buffer(缓冲区)是Java NIO中提供的用于存储数据的容器,底层依靠数组来存储数据,并提供了对数据的结构化访问以及维护读写位置等信息的功能
  • Channel(通道)是Java NIO中提供的用于传输数据的工具,代表了源节点和目标节点之间的数据通道
  • Selector 是 Java NIO 提供的一个组件,它可以检查一个或多个Channel 实例,并确定哪些通道准备好用于读、写等操作,通过这种方式,单个线程可以管理多个通道,从而管理多个网络连接

http://www.ppmy.cn/server/24009.html

相关文章

解决Oracle锁表的方法

在实际工作中&#xff0c;并发量比较大的项目&#xff0c;经常会出现锁表的问题&#xff0c;下面我将复现这个问题&#xff0c;并给出解决方法。 一、问题复现 1、session1修改aabb表的B字段为迪迦奥特曼&#xff0c;但是不提交该事务。 2、session2也修改这行的这个字段。 发…

AIGC:开启内容创作新纪元,我们如何看待它的影响与前景?

AIGC的概念 AIGC&#xff08;Artificial Intelligence Generated Content&#xff09;的概念主要是指人工智能生成内容。 这是一种新的人工智能技术&#xff0c;它利用人工智能模型&#xff0c;根据给定的主题、关键词、格式、风格等条件&#xff0c;自动生成各种类型的文本、图…

gateway全局token过滤器

添加gateway依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency>创建一个tokenFilter 实现全局过滤器GlobalFilter,并且实现fitler方法 Value("${…

php7.4在foreach中对使用数据使用无法??[]判读,无法使用引用传递

代码如下图&#xff1a;这样子在foreach中是无法修改class_history的。正确的应该是去掉??[]判断。 public function actionY(){$array [name>aaa,class_history>[[class_name>一班,class_num>1],[class_name>二班,class_num>2]]];foreach ($array[class_…

linux第八章 git连接本地仓库和gitee

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…

机器人技术概述_1.机器人的概念及定义和发展历程

1.机器人的概念 机器人的英文名词是Robot&#xff0c;Robot一词最早出现在1920年捷克作家卡雷尔•卡佩克&#xff08;Karel Capek&#xff09;所写的剧本中&#xff0c;剧中的人造劳动者取名为Robot&#xff0c;捷克语的意思是“苦力”“奴隶”。英语的Robot一词就是由此而来的…

mysql面试题九(SQL优化)

目录 1.一条 SQL 是如何执行的 2.索引失效的几种情况 3.EXPLAIN 4.Where 子句如何优化 5.超大分页或深度分页如何处理 6.大表查询如何优化 7.分库分表 基本概念 分库分表方法 水平拆分 垂直拆分 分库分表后的注意事项 1.一条 SQL 是如何执行的 在MySQL中&#xff0…

Docker基础+虚拟化概念

目录 一、虚拟化简介 1、虚拟化概述 2、cpu的时间分片&#xff08;cpu虚拟化&#xff09; 3、cpu虚拟化性性能瓶颈 4、虚拟化工作 4.1虚拟机工作原理 4.2两大核心组件:QEMU、KVM 4.2.1QEMU&#xff1a; 4.2.2KVM&#xff1a; 5、虚拟化类型 ①全虚拟化&#xff1a; …