探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码

news2024/10/12 9:29:46

一、NIO简介

Java NIO(New IO)是Java SE 1.4引入的一个新的IO API,它提供了比传统IO更高效、更灵活的IO操作。与传统IO相比,Java NIO的优势在于它支持非阻塞IO和选择器(Selector)等特性,能够更好地支持高并发、高吞吐量的应用场景。

上图是官方对NIO的说明,Java NIO 官方通常被称为 New I/O(新I/O),但它也因其核心功能非阻塞 I/O 特性而常常被称为 Non-blocking I/O(非阻塞 I/O)。这两个术语在讨论 Java NIO 时都是正确的,它们描述了 Java 中用于处理非阻塞 I/O 操作的机制。

二、Java I/O发展史

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方式,在处理大量并发请求时具有优势。

三、NIO 的原理

1、核心概念

NIO 的核心概念是通道 (Channel)、缓冲区 (Buffer) 和选择器 (Selector)。

  • 通道(Channel)

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

  • 缓冲区(Buffer)

在Java NIO中,所有数据都是通过缓冲区对象进行传输的。缓冲区是一段连续的内存块,可以保存需要读写的数据。缓冲区对象包含了一些状态变量,例如容量(capacity)、限制(limit)、位置(position)等,用于控制数据的读写。

  • 选择器(Selector)

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

2、原理分解

p.s.通常我们看到的图会有thread及client两部分,不太好理解nio主要应用到多个网络通信场景,所以把socketserver-->socketclient画出来就更好理解了。

说到Java NIO大家都会想到上面这张图,NIO应用程序的工作流程如下:

  1. 创建通道:打开一个或多个通道,例如FileChannel、SocketChannel等。
  2. 创建缓冲区:为每个通道创建一个或多个缓冲区,用于读取或写入数据。
  3. 注册通道:将通道注册到选择器,以便选择器可以监控这些通道的状态。
  4. 选择就绪通道:选择器等待通道就绪事件,一旦有通道准备好进行I/O操作,选择器将通知应用程序。
  5. 读取/写入数据:应用程序从通道读取数据,或将数据写入通道,使用缓冲区来传输数据。

其中

  • 通道和缓冲区是一对一的关系。每个通道都有一个与之对应的缓冲区,用于存储数据。
  • 选择器(Selector)可以同时监视多个通道的状态。一个选择器可以绑定多个通道,以实现多路复用。

3、代码示例

在jdk的安装包里,这个路径JAVA_HOME/sample我们可以找到nio相应的示例代码。

3.1、简单操作

对于简单的文件操作,通常不需要使用选择器。传统的文件I/O操作(如文件读取和写入)可以通过FileChannel等通道进行,但它们不涉及到多路复用,因为文件读写通常是同步的,不需要监视多个通道的状态(如下面demo中的:inChannel.read(byteBuffer)本身还是一个阻塞的方法)。在这种情况下,选择器并不提供额外的好处。

以下是一个简单的Java NIO示例,演示如何从文件中读取数据并打印到控制台:

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NIOReadFileExample {
    public static void main(String[] args) {
        // 创建通道,使用 try-with-resources 语句自动关闭资源
        try (FileInputStream inputStream = new FileInputStream("source-file-path");
             FileChannel inChannel = inputStream.getChannel()) {
             // 创建一个缓冲区
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            // 使用 while 循环读取文件中的所有数据
            while (inChannel.read(byteBuffer) != -1) {
                // 切换到读模式
                byteBuffer.flip();
                // 读取缓冲区中的数据
                byte[] bytes = new byte[byteBuffer.limit()];
                byteBuffer.get(bytes);
                System.out.println(new String(bytes));
                // 清空缓冲区
                byteBuffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,我们首先打开一个文件通道,然后创建一个ByteBuffer来读取数据。我们使用read()方法从文件通道读取数据到缓冲区,然后使用flip()方法切换到读模式,遍历缓冲区并打印数据。最后,我们使用clear()方法清空缓冲区,切换到写模式,以便继续读取数据。最后,我们关闭通道以释放资源。这是一个简单的示例,实际应用中可能需要更多的错误处理和完善。

Java NIO的使用流程通常包括以下步骤:

  1. 打开通道(Channel):首先,你需要打开一个通道,可以是文件通道、套接字通道等。这通常通过FileChannel.open()或SocketChannel.open()等方法实现。
  2. 创建缓冲区(Buffer):接下来,创建一个或多个缓冲区,用于在通道和应用程序之间传输数据。常见的缓冲区包括ByteBuffer、CharBuffer、IntBuffer等。使用ByteBuffer.allocate()或ByteBuffer.allocateDirect()来创建缓冲区。
  3. 读取/写入数据:使用通道的read()方法来从通道读取数据到缓冲区,或使用write()方法将数据从缓冲区写入通道。对于套接字通道,你可以通过网络发送或接收数据。
  4. 缓冲区操作:对缓冲区进行操作,例如读取或写入数据。你可以使用get()方法来获取数据,使用put()方法来写入数据。
  5. 切换缓冲区:在读取数据后,通常需要切换缓冲区的读模式(flip),然后开始从缓冲区读取数据。同样,在写入数据后,切换缓冲区的写模式(flip)。
  6. 关闭通道:当操作完成后,关闭通道以释放资源,使用通道的close()方法来实现。

3.2、多个网络通信

对于简单的文件操作,通常不需要使用选择器。传统的文件I/O操作(如文件读取和写入)可以通过FileChannel等通道进行,但它们不涉及到多路复用,因为文件读写通常是同步的,不需要监视多个通道的状态。在这种情况下,选择器并不提供额外的好处。

在官方jdk中的exaples中我们可以看到Server有5个子类

  • B1:阻塞式单线程服务器

Blocking/Single-threaded Server

B1一个阻塞式单线程服务器,在完全服务于一个连接之前不会移动到下一个连接。一个线程来处理所有客户端请求。当等待来自客户端的数据或向客户端写入数据时,服务器将阻塞。这意味着服务器一次只能处理一个客户端请求。

这种类型的服务器简单易于实现,但可扩展性差。这是因为服务器一次只能处理一个客户端请求。如果有许多客户端请求数据,服务器将无法快速响应所有请求。

  • BN:阻塞式多线程服务器

Blocking/Multi-threaded Server

一个阻塞式多线程服务器,为每个连接创建一个新线程。

服务器将创建多个线程来处理客户端请求。当一个客户端连接到服务器时,服务器使用一个线程来处理该客户端的请求。

这种类型的服务器比阻塞式单线程服务器具有更好的可扩展性,但它仍然不是非常有效率,因为每个连接都需要一个单独的线程。对于大量连接来说,这会导致大量的线程开销。

  • BP:阻塞式线程池服务器

Blocking/Pooled-thread Server
一个多线程服务器,为服务器使用创建一个线程池。线程池决定如何调度这些线程。服务器将创建一个线程池来处理客户端请求。当一个客户端连接到服务器时,服务器将从线程池中获取一个线程来处理该客户端的请求。当请求处理完毕后,该线程将关闭连接并返回到线程池。

  • N1:非阻塞式单线程服务器

Nonblocking/Single-threaded Server

一个非阻塞式单线程服务器。所有 accept() 和 read()/write() 操作都由一个线程执行,但仅在被 Selector 选中执行这些操作后才执行。服务器将使用 Selector 来监控多个通道的就绪状态。当一个通道就绪时,服务器将从该通道读取数据或向该通道写入数据。

  • N2:非阻塞式双线程服务器

Nonblocking/Dual-threaded Server

一个非阻塞式双线程服务器,在一个线程中执行 accept() 操作,在另一个线程中处理请求。这两个线程都使用 select() 函数。

以下是SocketServer、SocketClient示例代码

SocketServer

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @Author: Hanko
 * @Date: 2023-10-12 17:42
 */
public class SelectorServer {
    public static void main(String[] args) throws IOException {
        // 创建一个服务器套接字通道
        ServerSocketChannel socketChannel = ServerSocketChannel.open();

        // 将服务器套接字通道绑定到指定端口
        socketChannel.bind(new InetSocketAddress(8888));

        // 将服务器套接字通道设置为非阻塞模式
        socketChannel.configureBlocking(false);

        // 创建一个缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 创建一个选择器
        Selector selector = Selector.open();

        // 将服务器套接字通道注册到选择器上,监听连接事件
        socketChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 循环判断通道已准备好进行I/O操作
        while (selector.select() > 0) {
            // 获取所有发生的SelectionKey
            Set<SelectionKey> selectionKeys = selector.selectedKeys();

            // 遍历所有SelectionKey
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                // 获取当前SelectionKey
                SelectionKey key = iterator.next();

                // 判断当前键的通道是否准备好接收socket连接
                if (key.isAcceptable()) {
                    // 接受客户端连接
                    SocketChannel sc = socketChannel.accept();

                    // 将客户端连接通道设置为非阻塞模式
                    sc.configureBlocking(false);

                    // 将客户端连接通道注册到选择器上,监听读事件
                    sc.register(selector, SelectionKey.OP_READ);
                    // 判断当前key的通道是否准备好读取操作
                } else if (key.isReadable()) {
                    // 获取当前key的通道
                    SocketChannel channel = (SocketChannel) key.channel();

                    // 从通道中读取数据到缓冲区
                    int len = 0;
                    while ((len = channel.read(buffer)) > 0) {
                        // 将缓冲区切换为读模式
                        buffer.flip();

                        // 打印缓冲区中的数据
                        System.out.println(new String(buffer.array(), 0, len));

                        // 将缓冲区重置为写模式
                        buffer.clear();
                    }
                }

                // 移除当前事件
                iterator.remove();
            }
        }
    }
}

SocketClient

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

    // 获取通道、绑定主机和端口
    SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 8888));

    // 切换到非阻塞模式
    socketChannel.configureBlocking(false);

    // 创建Buffer写入数据
    ByteBuffer buffer = ByteBuffer.allocate(1024);

    // 将当前时间写入缓冲区
    buffer.put(new Date().toString().getBytes());

    // 将缓冲区切换为写模式
    buffer.flip();

    // 将数据写入通道
    socketChannel.write(buffer);

    // 关闭通道
    socketChannel.close();
}

四、NIO 的应用场景

NIO 适用于以下场景:

  • 网络通信:NIO 可以用于开发高并发的网络应用,例如 Web 服务器、游戏服务器等。
  • 文件操作:NIO 可以用于开发高性能的文件操作应用,例如文件传输、文件压缩等。
  • 进程间通信:NIO 可以用于实现进程间通信,例如共享内存、管道等。
  • 数据库操作:NIO 可以用于提高数据库操作的性能,例如批量插入、批量查询等。

1、在业务中的应用

  • 聊天服务器:使用 NIO 来建立和维护多个客户端连接,并高效地处理客户端请求。
  • 文件传输:使用 NIO 来高效地传输大文件。
  • 数据库操作:使用 NIO 来批量插入或查询数据,提高数据库操作的性能。

2、在框架中的应用

  • Netty

这是一个基于java nio实现的高性能、高可靠性的网络框架,它提供了一系列的组件和工具,用于构建异步、事件驱动的网络应用。Netty被广泛应用在互联网、大数据、游戏、通信等领域,一些著名的开源项目如Dubbo、Zookeeper、RocketMQ、Elasticsearch等都基于Netty构建 。

  • Mina

这是一个基于java nio实现的轻量级网络框架,它支持TCP、UDP、SSL等协议,以及多种编解码器和过滤器。Mina可以用于开发高性能的网络服务器和客户端,一些开源项目如Apache Directory Server、Apache James等都使用了Mina 。

  • Jetty

这是一个基于java nio实现的Web服务器和Servlet容器,它支持HTTP/2、WebSocket等协议,以及反应式编程模型。Jetty可以嵌入到其他应用中,提供Web服务和Web界面,一些开源项目如Eclipse、Hadoop等都使用了Jetty 。

五、优缺点

1、NIO 的优势

NIO 相对于传统 IO 具有以下优势:

  • 提高并发性:NIO 可以使用多路复用器来监听多个通道的事件,提高并发性。
  • 提高性能:NIO 支持非阻塞 IO,可以提高性能。
  • 简化编程:NIO 的 API 更加简洁,易于理解和使用。

2、NIO 的缺点

NIO 相对于传统 IO 具有以下缺点:

  • 学习成本较高:NIO 的概念和 API 与传统 IO 不同,学习成本较高。
  • 不兼容性:NIO 与传统 IO 存在不兼容性,需要注意兼容性问题。

为什么NIO没有广泛的被推广起来呢?

  1. 复杂性:相对于传统的阻塞式I/O,Java NIO 的编程模型更加复杂。它需要开发人员处理事件、缓冲区管理、选择器等概念,这可能会增加学习曲线,尤其是对于新手来说。
  2. 性能优势局限:Java NIO 在高并发和高吞吐量的场景下可以提供性能优势,但对于许多常规应用程序而言,传统的阻塞式I/O 已经足够了。只有需要处理大量并发连接或需要高度定制化的网络通信时,Java NIO 才会显得更有价值。
  3. 第三方库的竞争:有一些第三方库和框架,如Netty和Apache MINA,构建在Java NIO 之上,提供了更易于使用的高性能网络通信解决方案。这些库可能更容易推广,而不是直接使用Java NIO。
  4. 历史原因:许多早期的Java应用程序是基于传统的阻塞式I/O构建的,而且迁移到Java NIO 可能需要重写或修改现有的代码。这使得许多遗留应用程序不愿意切换到新的I/O模型。

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

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

相关文章

基于下垂控制的孤岛双机并联逆变器环流抑制模型(Simulink仿真实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

分享一个Redis自带的压测工具:redis-benchmark

前言 今天给xdm分享一个Redis自带的压测工具&#xff1a;redis-benchmark。 介绍 redis-benchmark 是一个用于测试 Redis性能的基准测试工具&#xff0c;可以帮助开发人员评估和比较 Redis 在不同配置或负载情况下的吞吐量和延迟。 使用 redis-benchmark 可以执行多种类型的…

如何将几个模型合并成一个

1、什么时候需要合并模型&#xff1f; 组装和装配&#xff1a;当你需要将多个零件或组件组装成一个整体时&#xff0c;可以合并它们成为一个模型。例如&#xff0c;在制造业中&#xff0c;当需要设计和展示一个完整的机械装置或产品时&#xff0c;可以将各个零部件合并成一个模…

从黑客帝国到元宇宙,“数字身份”之船终将驶向何方?

1999年第一部《黑客帝国》在北美上映&#xff0c;当时正值千禧年初&#xff0c;在这个互联网并未全民普及的年代&#xff0c;《黑客帝国》超前地抛出在蓝、红药丸&#xff08;即虚幻与真实&#xff09;之间作选择的经典问题&#xff0c;表达人类与互联网之间的虚实关系&#xf…

c 语言基础:L1-044 稳赢

大家应该都会玩“锤子剪刀布”的游戏&#xff1a;两人同时给出手势&#xff0c;胜负规则如图所示&#xff1a; 现要求你编写一个稳赢不输的程序&#xff0c;根据对方的出招&#xff0c;给出对应的赢招。但是&#xff01;为了不让对方输得太惨&#xff0c;你需要每隔K次就让一个…

对传感器采样数据的低通滤波

低通滤波(Low-pass filter) 是一种过滤方式&#xff0c;规则为低频信号能正常通过&#xff0c;而超过设定临界值的高频信号则被阻隔、减弱。 一阶低通数字滤波器 滤波系数a越小&#xff0c;滤波结果越平稳&#xff0c;但是灵敏度低&#xff1b;滤波系数a越大&#xff0c;滤波结…

LeetCode算法刷题(python) Day43|09动态规划|343. 整数拆分、96.不同的二叉搜索树

目录 LeetCode 343. 整数拆分LeetCode 96.不同的二叉搜索树 LeetCode 343. 整数拆分 力扣题目链接 正整数n&#xff0c;先拆成两个数i, n-i&#xff0c;拆成多个数可以对n-i进行拆分&#xff0c;然后对这些求最大值。 确定dp数组以及下标的含义&#xff1a;对i进行拆分的最大…

ch552g下载时遇到的可能问题

第一次出现的问题 首先确定&#xff0c;vcc和v33&#xff0c;示例芯片中的是15和16引脚都并联了去耦电容。 烧录一般使用上面的两个软件。第一次出现的问题&#xff0c;当没有打开这两个烧录软件时&#xff0c;连接usb数据线后电脑循环出现识别和断开的情况。当打开软件后就能…

通讯网关软件028——利用CommGate X2Modbus实现Modbus RTU访问PI服务器

本文介绍利用CommGate X2Modbus实现Modbus RTU访问PI数据库。CommGate X2MODBUS是宁波科安网信开发的网关软件&#xff0c;软件可以登录到网信智汇(http://wangxinzhihui.com)下载。 【案例】如下图所示&#xff0c;实现上位机通过Modbus RTU来获取PI数据库的数据。 【解决方案…

计算机算法分析与设计(12)---贪心算法(最优装载问题和哈夫曼编码问题)

文章目录 一、最优装载问题1.1 问题表述1.2 代码编写 二、哈夫曼编码2.1 哈夫曼编码概述2.2 前缀码2.3 问题描述2.4 代码思路2.5 代码编写 一、最优装载问题 1.1 问题表述 1. 有一批集装箱要装上一艘载重量为 c c c 的轮船&#xff0c;已知集装箱 i ( 1 ≤ i ≤ n ) i(1≤i≤…

神经网络中的反向传播:综合指南

塔曼纳 一、说明 反向传播是人工神经网络 &#xff08;ANN&#xff09; 中用于训练深度学习模型的流行算法。它是一种监督学习技术&#xff0c;用于调整网络中神经元的权重&#xff0c;以最小化预测输出和实际输出之间的误差。 在神经网络中&#xff0c;反向传播是计算损失函数…

纳米软件科普|半导体常见的测试种类详解

纳米软件科普下半导体几种常见的测试方法种类的详细介绍&#xff0c;有你不了解的吗&#xff1f; 外观检测&#xff1a;这一步骤主要是对半导体外观质量的评估。它包括检查芯片的平整度、颜色、镜面度等&#xff0c;以确保半导体表面无明显缺陷或不规则形状。 电性能测试&…

Kafka 使用java实现,快速入门

一、kafka的生产者和消费者 1. 生产者发送消息的流程 2. 消费者接收消息的流程 二、 java 代码实现 1. 添加依赖&#xff1a; <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka_2.12</artifactId></dependency>2. 实现…

vue3+koa+axios实现前后端通信

vue3koaaxios实现前后端通信 写了一个小demo来实现前后端通信,涉及跨域问题&#xff0c;非常简单可以给大家平时开发的时候参考 服务端&#xff1a; 目录结构如下&#xff1a; router index.js // router的入口文件 // 引入路由 const Router require("koa-router&quo…

React组件渲染和更新的过程

一、回顾Vue组件渲染和更新的过程 二、回顾JSX本质和vdom 三、组件渲染和更新 1、组件渲染过程 props state (组件有了props state)render()生成vnodepatch(elem, vnode) 2、组件更新过程 setState(newState) --> dirtyComponents (可能有子组件)render()生成newVnodepa…

3、Flowable任务分配和流程变量

任务分配和流程变量 1.任务分配 1.1 固定分配 固定分配就是我们前面介绍的&#xff0c;在绘制流程图或者直接在流程文件中通过Assignee来指定的方式 1.2 表达式分配 Flowable使用UEL进行表达式解析。UEL代表Unified Expression Language&#xff0c;是EE6规范的一部分.Flo…

通配符SSL证书价格贵吗

通配符SSL证书是一种SSL证书&#xff0c;用于保护基于相同域名但具有不同子域名的多个网站。 它使用通配符字符 "*" 来代表所有可能的二级子域名。JoySSL注册码230609领取免费使用通配符证书 通配符SSL证书的作用是使您可以使用单个证书来保护主域名和所有其下的子域…

千兆光模块和万兆光模块的区别?

在网络通信领域&#xff0c;千兆光模块和万兆光模块是最为常见且广泛应用的两种光模块。不同之处在于传输速率、封装、传输距离、功耗、发射光功率、接收光功率和应用场景等。 千兆光模块的传输速率为1 Gbps&#xff0c;万兆光模块的传输速率为10 Gbps&#xff0c;这意味着万…

柯桥基础俄语口语|初级俄语学习:用联想法学习字母

初级俄语 用联想法学习字母 开始学习俄语的时候&#xff0c;第一任务就是掌握字母表。今天彼得俄语会帮助大家巧记俄语字母表。为此&#xff0c;小编把所有字母分成三组&#xff1a;与拼音相似的&#xff0c;可以用图片记住的以及可以用汉字记住的。 与拼音相似的字母 俄语有3…

数据结构与算法-(9)---双端队列(Deque)

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…