NIO 与传统 IO:深入理解与应用场景

在 Java 编程中,IO(输入/输出)操作是不可或缺的一部分。Java 提供了两种主要的 IO 机制:传统的阻塞式 IO(Blocking IO)和非阻塞式 IO(Non-blocking IO),后者通常被称为 NIO(New IO)。本文将深入探讨这两种 IO 模型的差异,特别是在文件操作和网络传输中的应用场景。

1 传统 IO 与 NIO 的对比

1.1 传统 IO(Blocking IO)

传统 IO 基于字节流或字符流(如 FileInputStreamBufferedReader 等)进行文件读写,以及使用 SocketServerSocket 进行网络传输。传统 IO 采用阻塞式模型,对于每个连接,都需要创建一个独立的线程来处理读写操作。当一个线程在等待 I/O 操作时,无法执行其他任务,这会导致大量线程的创建和销毁,以及上下文切换,降低了系统性能。

1.2 NIO(Non-blocking IO)

NIO 使用通道(Channel)和缓冲区(Buffer)进行文件操作,以及使用 SocketChannelServerSocketChannel 进行网络传输。NIO 采用非阻塞模型,允许线程在等待 I/O 时执行其他任务。这种模式通过使用选择器(Selector)来监控多个通道(Channel)上的 I/O 事件,实现了更高的性能和可伸缩性。

2 文件操作中的 NIO 与传统 IO

2.1 性能测试

为了比较 NIO 和传统 IO 在文件操作中的性能,我们编写了一个简单的文件复制程序,分别使用传统 IO 和 NIO 进行文件复制。

public class SimpleFileTransferTest {

    // 使用传统的 I/O 方法传输文件
    private long transferFile(File source, File des) throws IOException {
        long startTime = System.currentTimeMillis();

        if (!des.exists())
            des.createNewFile();

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(des));

        byte[] bytes = new byte[1024 * 1024];
        int len;
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, len);
        }

        long endTime = System.currentTimeMillis();
        return endTime - startTime;
    }

    // 使用 NIO 方法传输文件
    private long transferFileWithNIO(File source, File des) throws IOException {
        long startTime = System.currentTimeMillis();

        if (!des.exists())
            des.createNewFile();

        RandomAccessFile read = new RandomAccessFile(source, "rw");
        RandomAccessFile write = new RandomAccessFile(des, "rw");

        FileChannel readChannel = read.getChannel();
        FileChannel writeChannel = write.getChannel();

        ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024);
        while (readChannel.read(byteBuffer) > 0) {
            byteBuffer.flip();
            writeChannel.write(byteBuffer);
            byteBuffer.clear();
        }

        writeChannel.close();
        readChannel.close();
        long endTime = System.currentTimeMillis();
        return endTime - startTime;
    }

    public static void main(String[] args) throws IOException {
        SimpleFileTransferTest simpleFileTransferTest = new SimpleFileTransferTest();
        File sourse = new File("[电影天堂www.dygod.cn]猜火车-cd1.rmvb");
        File des = new File("io.avi");
        File nio = new File("nio.avi");

        long time = simpleFileTransferTest.transferFile(sourse, des);
        System.out.println(time + ":普通字节流时间");

        long timeNio = simpleFileTransferTest.transferFileWithNIO(sourse, nio);
        System.out.println(timeNio + ":NIO时间");
    }
}

测试结果:在文件较大的情况下,传统 IO 的速度竟然比 NIO 更快。这可能是因为文件操作本身不涉及大量并发,NIO 的非阻塞特性在文件操作中并没有明显优势。

3. 网络传输中的 NIO 与传统 IO

在 Java 中,传统 IO 和 NIO 在服务器端实现上有显著的差异。传统 IO 使用阻塞式模型,而 NIO 使用非阻塞式模型,通过 Selector 实现 I/O 多路复用。下面我们将详细对比这两种模型的实现。

3.1 服务器端代码对比

传统 IO 服务器:传统 IO 服务器使用 ServerSocket 和 Socket 类来实现阻塞式 I/O。每个连接都需要一个单独的线程来处理,这在大规模并发连接的情况下会导致性能问题。

public class IOServer {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8080);

            while (true) {
                Socket client = serverSocket.accept();
                InputStream in = client.getInputStream();
                OutputStream out = client.getOutputStream();

                byte[] buffer = new byte[1024];
                int bytesRead = in.read(buffer);
                out.write(buffer, 0, bytesRead);

                in.close();
                out.close();
                client.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

关键点:

阻塞式 I/O:serverSocket.accept() in.read(buffer) 都是阻塞的,直到有新的连接或数据到达。
线程模型:每个连接都需要一个单独的线程来处理,这在大规模并发连接的情况下会导致性能问题。

NIO 服务器:NIO 服务器使用 ServerSocketChannel 和 Selector 类来实现非阻塞式 I/O 和 I/O 多路复用。单个线程可以处理多个连接,从而提高性能。

public class NIOServer {
    public static void main(String[] args) {
        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(8081));
            serverSocketChannel.configureBlocking(false);

            Selector selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while (true) {
                selector.select();
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();

                    if (key.isAcceptable()) {
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel client = server.accept();
                        client.configureBlocking(false);
                        client.register(selector, SelectionKey.OP_READ);
                    } else if (key.isReadable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        client.read(buffer);
                        buffer.flip();
                        client.write(buffer);
                        client.close();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

关键点:

非阻塞式 I/O:serverSocketChannel.configureBlocking(false)client.configureBlocking(false) 设置为非阻塞模式。
I/O 多路复用:使用 Selector 监控多个 SocketChannel,单个线程可以处理多个连接。
事件驱动:通过SelectionKey处理不同的事件(如OP_ACCEPTOP_READ)。

客户端测试用例:为了比较传统 IO 和 NIO 服务器的性能,我们编写了一个简单的客户端测试用例,分别测试处理 10000 个客户端请求所需的时间。

public class TestClient {
    public static void main(String[] args) throws InterruptedException {
        int clientCount = 10000;
        ExecutorService executorServiceIO = Executors.newFixedThreadPool(10);
        ExecutorService executorServiceNIO = Executors.newFixedThreadPool(10);

        Runnable ioClient = () -> {
            try {
                Socket socket = new Socket("localhost", 8080);
                OutputStream out = socket.getOutputStream();
                InputStream in = socket.getInputStream();
                out.write("Hello, 沉默王二 IO!".getBytes());
                byte[] buffer = new byte[1024];
                in.read(buffer);
                in.close();
                out.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        };

        Runnable nioClient = () -> {
            try {
                SocketChannel socketChannel = SocketChannel.open();
                socketChannel.connect(new InetSocketAddress("localhost", 8081));
                ByteBuffer buffer = ByteBuffer.wrap("Hello, 沉默王二 NIO!".getBytes());
                socketChannel.write(buffer);
                buffer.clear();
                socketChannel.read(buffer);
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        };

        long startTime, endTime;

        startTime = System.currentTimeMillis();
        for (int i = 0; i < clientCount; i++) {
            executorServiceIO.execute(ioClient);
        }
        executorServiceIO.shutdown();
        executorServiceIO.awaitTermination(1, TimeUnit.MINUTES);
        endTime = System.currentTimeMillis();
        System.out.println("传统 IO 服务器处理 " + clientCount + " 个客户端耗时: " + (endTime - startTime) + "ms");

        startTime = System.currentTimeMillis();
        for (int i = 0; i < clientCount; i++) {
            executorServiceNIO.execute(nioClient);
        }
        executorServiceNIO.shutdown();
        executorServiceNIO.awaitTermination(1, TimeUnit.MINUTES);
        endTime = System.currentTimeMillis();
        System.out.println("NIO 服务器处理 " + clientCount + " 个客户端耗时: " + (endTime - startTime) + "ms");
    }
}

测试结果:NIO 服务器处理 10000 个客户端请求的时间明显优于传统 IO 服务器,NIO 在网络传输中的性能优势显著。

4. 总结

  • 文件操作:传统 IO 和 NIO 在文件操作中的性能差异不大,NIO 的非阻塞特性在文件操作中没有明显优势。
  • 网络传输:NIO 在网络传输中的性能显著优于传统 IO,特别是在高并发场景下。NIO 的非阻塞模型和 I/O 多路复用机制使得单个线程可以高效地管理多个并发连接,从而提高系统性能。
  • 传统 I/O 采用阻塞式模型,线程在 I/O 操作期间无法执行其他任务。NIO 使用非阻塞模型,允许线程在等待 I/O 时执行其他任务,通过选择器(Selector)监控多个通道(Channel)上的 I/O 事件,提高性能和可伸缩性。
  • 传统 I/O 使用基于字节流或字符流的类(如 FileInputStreamBufferedReader 等)进行文件读写。NIO 使用通道(Channel)和缓冲区(Buffer)进行文件操作,NIO 在性能上的优势并不大。
  • 传统 I/O 使用 SocketServerSocket 进行网络传输,存在阻塞问题。NIO 提供了 SocketChannel ServerSocketChannel,支持非阻塞网络传输,提高了并发处理能力。

理解 NIO 和传统 IO 的差异及其适用场景,有助于在实际开发中选择合适的 IO 机制,以提高程序的性能和可扩展性。

5 思维导图

在这里插入图片描述

6 参考链接

Java NIO 比传统 IO 强在哪里?

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VMware高危漏洞VMSA-2024-0019修复堆溢出和权限提升漏洞

一、概述 VMware vCenter Server 高危漏洞&#xff08;CVE-2024-38812、CVE-2024-38813&#xff09;再次受到攻击&#xff0c;需要升级补丁&#xff0c;详情查看之前文章紧急通告VMware vCenter高危漏洞CVE-2024-38812和CVE-2024-38813修复方案 再次更新了漏洞 二、漏洞影像描…

什么是Hadoop

Hadoop 介绍 Hadoop 是由 Apache 开发的开源框架&#xff0c;用于处理分布式环境中的海量数据。Hadoop 使用 Java 编写&#xff0c;通过简单的编程模型允许在集群中进行大规模数据集的存储和计算。它具备高可靠性、容错性和扩展性。 分布式存储&#xff1a;Hadoop 支持跨集群…

038集——quadtree(CAD—C#二次开发入门)

效果如下&#xff1a; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using System; using System.Collections.Generic; using System.Linq; using System.T…

ISUP协议视频平台EasyCVR私有化视频平台新能源汽车充电停车管理方案的创新与实践

在环保意识提升和能源转型的大背景下&#xff0c;新能源汽车作为低碳出行的选择&#xff0c;正在全球迅速推广。但这种快速增长也引发了充电基础设施短缺和停车秩序混乱等挑战&#xff0c;特别是在城市中心和人口密集的居住区&#xff0c;这些问题更加明显。因此&#xff0c;开…

Spring Boot中使用AOP和反射机制设计一个的幂等注解(两种持久化模式),简单易懂教程

该帖子介绍如何设计利用AOP设计幂等注解&#xff0c;且可设置两种持久化模式 1、普通模式&#xff1a;基于redis的幂等注解&#xff0c;持久化程度较低 2、增强模式&#xff1a;基于数据库&#xff08;MySQL&#xff09;的幂等注解&#xff0c;持久化程度高 如果只需要具有re…

算法编程题-网格中的最短路径

算法编程题-网格中的最短路径 原题描述思路简述代码实现[^1]复杂度分析 原题描述 LeetCode 1293 网格中的最短路径&#xff1a;给定一个m * n的网格&#xff0c;网格中的每一个点的值为0&#xff08;无障碍&#xff09;&#xff0c;为1&#xff08;有障碍&#xff09;&#xf…

Xcode 项目内 OC 混编 Python,调用 Python 函数,并获取返回值(基于 python 的 c函数库)

1:新建 Xcode 工程 2:工程添加 Python.framework 1597052861430.jpg 3:在当前工程下新建一个名字为 googleT 的 python 文件(googleT.py) 1597052584962.jpg 在 googleT.py 文件内写入一个测试 python 函数 def lgf_translate( str ):var1 Hello World!print (str var1)retu…

蓝桥杯每日真题 - 第16天

题目&#xff1a;&#xff08;卡牌&#xff09; 题目描述&#xff08;13届 C&C B组C题&#xff09; 解题思路&#xff1a; 题目分析&#xff1a; 有 n 种卡牌&#xff0c;每种卡牌的现有数量为 a[i]&#xff0c;所需的最大数量为 b[i]&#xff0c;还有 m 张空白卡牌。 每…

计算机网络——路由选择算法

路由算法 路由的计算都是以子网为单位计算的——找到从原子网到目标子网的路径 链路状态算法 序号——&#xff08;源路由器&#xff0c;序号&#xff09;——如果发现这个序号重复或者老了——就不扩散 先测量——再泛洪获得路由 路由转发情况 若S——>W是21则不更改——…

Android - Pixel 6a 手机OS 由 Android 15 降级到 Android 14 操作记录

Pixel 6a 手机由 Android 14 升级到 Android 15了&#xff0c;但是由于一些原因又想降级回 Android 14&#xff0c; 能降吗&#xff1f;该怎么降级呢&#xff1f;本篇文章来记述实际操作过程&#xff0c;希望能给想做相同操作的人一些帮助。 答案当然是能降&#xff0c;而且我…

SpringBoot+React养老院管理系统 附带详细运行指导视频

文章目录 一、项目演示二、项目介绍三、运行截图四、主要代码1.入住合同文件上传2.添加和修改套餐的代码3.查看入住记录代码 一、项目演示 项目演示地址&#xff1a; 视频地址 二、项目介绍 项目描述&#xff1a;这是一个基于SpringBootReact框架开发的养老院管理系统。首先…

Ubuntu安装ollama,并运行ollama和通义千问,使用gradio做界面

Ubuntu安装ollama&#xff0c;并运行ollama和通义千问 安装ollama方式一&#xff1a;方式二 下载安装模型运行大模型运行ollama服务前端的实现python环境安装修改pip国内源前端页面搭建测试前后端联通设计完整的ui 安装ollama 方式一&#xff1a; 访问网站连接&#xff0c;选…

【微软:多模态基础模型】(3)视觉生成

欢迎关注[【youcans的AGI学习笔记】](https://blog.csdn.net/youcans/category_12244543.html&#xff09;原创作品 【微软&#xff1a;多模态基础模型】&#xff08;1&#xff09;从专家到通用助手 【微软&#xff1a;多模态基础模型】&#xff08;2&#xff09;视觉理解 【微…

前端研发高德地图,如何根据经纬度获取地点名称和两点之间的距离?

地理编码与逆地理编码 引入插件&#xff0c;此示例采用异步引入&#xff0c;更多引入方式 https://lbs.amap.com/api/javascript-api-v2/guide/abc/plugins AMap.plugin("AMap.Geocoder", function () {var geocoder new AMap.Geocoder({city: "010", /…

Linux上使用SELinux保护网络服务

前言 SELinux&#xff08;Security-Enhanced Linux&#xff09;是一种安全模块&#xff0c;用于增强基于 Linux 的操作系统的安全性。 它通过强制访问控制&#xff08;MAC&#xff09;机制来限制进程和用户对系统资源的访问权限&#xff0c;从而防止未经授权的操作。 在 SELin…

【Linux】僵尸进程、进程状态简介

本文内容均来自个人笔记并重新梳理&#xff0c;如有错误欢迎指正&#xff01; 如果对您有帮助&#xff0c;烦请点赞、关注、转发、订阅专栏&#xff01; 专栏订阅入口 | 精选文章 | Kubernetes | Docker | Linux | 羊毛资源 | 工具推荐 | 往期精彩文章 【Docker】&#xff08;全…

uniapp 选择 省市区 省市 以及 回显

从gitee仓库可以拿到demo 以及 json省市区 文件 // 这是组件部分 <template><uni-popup ref"popup" type"bottom"><view class"popup"><view class"picker-btn"><view class"left" click"…

Unity Dots下的动画合批工具:GPU ECS Animation Baker

书接上文&#xff0c;为了实现大批量物体的生成&#xff0c;我们准备使用Unity最新的dots系统&#xff0c;在该系统下找到了动画解决方案&#xff1a;GPU ECS Animation Baker。 导入的同时&#xff0c;也需要导入以下两个插件&#xff0c;否则会提示报错&#xff1a; PS&…

windows上部署flask程序

文章目录 前言一、准备工作二、配置 Gunicorn 或 uWSGI1.安装 Waitress2.修改启动文件来使用 Waitress 启动 Flask 应用3.配置反向代理&#xff08;可选&#xff09;4.启动程序访问 三.Flask 程序在 Windows 启动时自动启动1.使用 nssm&#xff08;Non-Sucking Service Manager…

共享单车管理系统项目学习实战

前言 Spring Boot Vue前后端分离 前端&#xff1a;Vue&#xff08;CDN&#xff09; Element axios(前后端交互) BaiDuMap ECharts(图表展示) 后端&#xff1a;Spring Boot Spring MVC(Web) MyBatis Plus(数据库) 数据库:MySQL 验证码请求