Java的IO模型详解-BIO,NIO,AIO

news2024/12/24 2:18:05

一、BIO相关知识

Java 的 BIO (Blocking I/O) 模型是基于传统的同步阻塞 I/O 操作。在这种模型中,每个客户端连接都需要一个独立的线程来处理请求。当线程正在执行 I/O 操作时(如读取或写入),它会被阻塞,直到操作完成。

读写模型

在这里插入图片描述

BIO 概述

BIO 是 Java 中最简单的 I/O 模型之一,它使用 java.io 包中的类来实现,例如 InputStream, OutputStream, Reader, 和 Writer。在 BIO 模型中,每当有客户端连接到来时,服务器就会创建一个新的线程来处理这个连接上的所有读写操作。

BIO 特点

  • 同步:读写操作是同步进行的,即必须等待一个操作完成后才能继续下一步操作。
  • 阻塞:当线程调用 I/O 方法时,如果数据没有准备好,线程就会被阻塞,直到数据准备好为止。
  • 每连接一线程:对于每一个客户端连接,服务器都会分配一个线程来处理该连接的所有 I/O 操作。
  • 简单易用:BIO 的实现比较简单,易于理解和使用。
  • 低效:在高并发的情况下,由于每个连接都需要一个线程,因此线程的创建和管理成本较高,可能导致服务器资源耗尽。

BIO 实现示例

下面是一个简单的 BIO 服务器端实现示例,该服务器接收客户端的连接,并打印出客户端发送的消息。

服务器端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class BioServer {
    public static void main(String[] args) {
        int port = 8080; // 服务器监听端口
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("Server started on port " + port);

            while (true) {
                Socket clientSocket = serverSocket.accept();
                new Thread(new ClientHandler(clientSocket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static class ClientHandler implements Runnable {
        private final Socket socket;

        public ClientHandler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                 PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
                String inputLine;
                while ((inputLine = in.readLine()) != null) {
                    System.out.println("Received from client: " + inputLine);
                    if ("bye".equalsIgnoreCase(inputLine)) {
                        break;
                    }
                    out.println("Echo: " + inputLine);
                }
                System.out.println("Client disconnected.");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
客户端

接下来是一个简单的 BIO 客户端实现示例,该客户端向服务器发送消息并接收服务器的响应。

import java.io.*;
import java.net.Socket;

public class BioClient {
    public static void main(String[] args) {
        String serverAddress = "localhost";
        int port = 8080;

        try (Socket socket = new Socket(serverAddress, port);
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
            out.println("Hello, server!");
            out.println("Another message.");
            out.println("bye");

            String response;
            while ((response = in.readLine()) != null) {
                System.out.println("Received from server: " + response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

二、NIO相关知识点

Java 的 NIO (New I/O) 模型是在 Java 1.4 中引入的,它提供了一种新的方式来处理输入输出操作,相比于传统的 BIO (Blocking I/O) 模型,NIO 提供了更高的性能和更灵活的编程模型。NIO 主要包括三个核心组件:缓冲区(Buffer)、通道(Channel) 和选择器(Selector)。

读写模型

在这里插入图片描述

NIO 核心概念

  • 缓冲区(Buffer):缓冲区用于存储不同类型的二进制数据。与流不同的是,缓冲区可以保存数据,可以对数据进行读写操作,也可以在缓冲区之间复制数据。在 NIO 中,所有数据都是通过缓冲区进行操作的。

  • 通道(Channel):通道类似于流,但是它可以双向读写数据。通道可以从文件系统、网络等地方获取或发送数据。与流不同的是,通道可以与缓冲区交互,也就是说数据可以被读取到缓冲区中,或者从缓冲区写入到通道中。

  • 选择器(Selector):选择器允许单个线程监控多个通道的状态,比如哪些通道是可读的、哪些是可写的。这使得一个线程可以同时处理多个客户端连接,大大提高了并发处理能力。

NIO 特点

  • 非阻塞:NIO 允许非阻塞 I/O 操作,即使没有数据可用,线程也不会被阻塞,而是可以继续执行其他任务。
  • 高效:由于使用了缓冲区和选择器,NIO 可以在一个线程中处理多个连接,减少了线程的创建和销毁所带来的开销。
  • 复杂性:相比于 BIO,NIO 的编程模型更为复杂,需要更深入地理解缓冲区和通道的概念。

NIO 实现示例

下面是一个简单的 NIO 服务器端实现示例,该服务器接收客户端的连接,并打印出客户端发送的消息。

服务器端
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;

public class NioServer {
    public static void main(String[] args) {
        int port = 8080; // 服务器监听端口
        try (Selector selector = Selector.open();
             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
            serverSocketChannel.bind(new InetSocketAddress(port));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("Server started on port " + port);

            while (true) {
                selector.select();

                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectedKeys.iterator();

                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();

                    if (key.isAcceptable()) {
                        acceptConnection(serverSocketChannel, selector);
                    } else if (key.isReadable()) {
                        readData(key);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void acceptConnection(ServerSocketChannel serverSocketChannel, Selector selector) throws IOException {
        SocketChannel clientChannel = serverSocketChannel.accept();
        clientChannel.configureBlocking(false);
        clientChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("Client connected: " + clientChannel.getRemoteAddress());
    }

    private static void readData(SelectionKey key) throws IOException {
        SocketChannel clientChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        int numRead;
        while ((numRead = clientChannel.read(buffer)) > 0) {
            buffer.flip();
            byte[] data = new byte[numRead];
            buffer.get(data);
            String received = new String(data, "UTF-8");
            System.out.println("Received from client: " + received);
            buffer.clear();
        }

        if (numRead == -1) { // 如果客户端关闭了连接
            clientChannel.close();
            key.cancel();
        }
    }
}
客户端

下面是一个简单的 NIO 客户端实现示例,该客户端向服务器发送消息并接收服务器的响应。

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;

public class NioClient {
    public static void main(String[] args) {
        String serverAddress = "localhost";
        int port = 8080;

        try (SocketChannel socketChannel = SocketChannel.open()) {
            socketChannel.connect(new InetSocketAddress(serverAddress, port));
            socketChannel.configureBlocking(false);

            ByteBuffer buffer = ByteBuffer.allocate(1024);

            buffer.put("Hello, server!".getBytes(StandardCharsets.UTF_8));
            buffer.flip();
            socketChannel.write(buffer);
            buffer.clear();

            buffer.put("Another message.".getBytes(StandardCharsets.UTF_8));
            buffer.flip();
            socketChannel.write(buffer);
            buffer.clear();

            buffer.put("bye".getBytes(StandardCharsets.UTF_8));
            buffer.flip();
            socketChannel.write(buffer);
            buffer.clear();

            while (socketChannel.isOpen()) {
                buffer.clear();
                int numRead = socketChannel.read(buffer);
                if (numRead == -1) {
                    break;
                }

                buffer.flip();
                byte[] data = new byte[numRead];
                buffer.get(data);
                System.out.println("Received from server: " + new String(data, StandardCharsets.UTF_8));
                buffer.clear();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

三、AIO相关知识

Java 的 AIO (Asynchronous I/O) 模型是在 Java 7 中引入的,它是 NIO 的扩展,支持真正的异步 I/O 操作。AIO 有时也被称作 NIO 2,因为它是 NIO 的后续版本,增强了 NIO 的功能。

读写模型

在这里插入图片描述

AIO 概念

AIO 的关键概念是异步非阻塞 I/O。在 AIO 中,应用程序发起一个 I/O 请求后,可以立即返回并继续执行其他任务,而不需要等待 I/O 操作完成。当 I/O 操作完成后,应用程序会被通知。

AIO 组件

AIO 主要涉及以下组件:

  • AsynchronousFileChannel:用于异步地读取、写入和映射文件。
  • AsynchronousSocketChannel:用于异步地处理网络连接。
  • AsynchronousServerSocketChannel:用于异步地接受新的连接。
  • CompletionHandler:用于处理异步 I/O 操作完成时的回调。

AIO 特点

  • 异步性:AIO 支持真正的异步 I/O 操作,这意味着线程可以在发起 I/O 操作后立即返回,而不是等待操作完成。
  • 非阻塞:线程不会因为等待 I/O 操作而被阻塞,可以继续执行其他任务。
  • 高并发:AIO 适合处理高并发的场景,因为它可以有效地利用少量线程处理大量的 I/O 操作。
  • 复杂性:相比 BIO 和 NIO,AIO 的编程模型更为复杂,需要熟悉异步编程的概念。

AIO 实现示例

下面是一个简单的 AIO 服务器端实现示例,该服务器接收客户端的连接,并打印出客户端发送的消息。

服务器端
import java.io.IOException;
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) {
        int port = 8080; // 服务器监听端口
        try (AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(port))) {
            System.out.println("Server started on port " + port);

            serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
                @Override
                public void completed(AsynchronousSocketChannel result, Void attachment) {
                    serverChannel.accept(null, this);

                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    result.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer attachment) {
                            attachment.flip();
                            byte[] data = new byte[attachment.remaining()];
                            attachment.get(data);
                            System.out.println("Received from client: " + new String(data));
                            result.write(ByteBuffer.wrap("Echo: ".getBytes()), ByteBuffer.wrap(data), new CompletionHandler<Integer, ByteBuffer>() {
                                @Override
                                public void completed(Integer result, ByteBuffer attachment) {
                                    attachment.flip();
                                    result.read(attachment, attachment, this);
                                }

                                @Override
                                public void failed(Throwable exc, ByteBuffer attachment) {
                                    exc.printStackTrace();
                                }
                            });
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer attachment) {
                            exc.printStackTrace();
                        }
                    });
                }

                @Override
                public void failed(Throwable exc, Void attachment) {
                    exc.printStackTrace();
                }
            });

            while (true) {
                Thread.sleep(1000); // 让服务器运行
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}
客户端

下面是一个简单的 AIO 客户端实现示例,该客户端向服务器发送消息并接收服务器的响应。

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class AioClient {
    public static void main(String[] args) {
        String serverAddress = "localhost";
        int port = 8080;

        try (AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open()) {
            socketChannel.connect(new InetSocketAddress(serverAddress, port), null, new CompletionHandler<Void, Void>() {
                @Override
                public void completed(Void result, Void attachment) {
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    buffer.put("Hello, server!".getBytes());
                    buffer.flip();
                    socketChannel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer attachment) {
                            attachment.clear();
                            buffer.put("Another message.".getBytes());
                            buffer.flip();
                            socketChannel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                                @Override
                                public void completed(Integer result, ByteBuffer attachment) {
                                    attachment.clear();
                                    buffer.put("bye".getBytes());
                                    buffer.flip();
                                    socketChannel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                                        @Override
                                        public void completed(Integer result, ByteBuffer attachment) {
                                            buffer.clear();
                                            socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                                                @Override
                                                public void completed(Integer result, ByteBuffer attachment) {
                                                    attachment.flip();
                                                    byte[] data = new byte[attachment.remaining()];
                                                    attachment.get(data);
                                                    System.out.println("Received from server: " + new String(data));
                                                    socketChannel.close();
                                                }

                                                @Override
                                                public void failed(Throwable exc, ByteBuffer attachment) {
                                                    exc.printStackTrace();
                                                }
                                            });
                                        }

                                        @Override
                                        public void failed(Throwable exc, ByteBuffer attachment) {
                                            exc.printStackTrace();
                                        }
                                    });
                                }

                                @Override
                                public void failed(Throwable exc, ByteBuffer attachment) {
                                    exc.printStackTrace();
                                }
                            });
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer attachment) {
                            exc.printStackTrace();
                        }
                    });
                }

                @Override
                public void failed(Throwable exc, Void attachment) {
                    exc.printStackTrace();
                }
            });

            while (true) {
                Thread.sleep(1000); // 让客户端运行
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

总结

为了便于类比,所以样例代码都是使用了客户端和服务端的交互代码实现。通过比对不难发现一下特点:

  • BIO:简单,易于理解,但在高并发情况下效率较低。
  1. 发起读请求
  2. 等待数据(阻塞)
  3. 返回(数据已经准备好了)
  • NIO:提高了效率,支持非阻塞 I/O,但编程复杂度增加。
  1. 发起读请求
  2. 请求返回,可以继续做自己的事情
  3. 问一下数据好了没?
  4. 没好, 直接返回
  5. 哈了, 等待数据拷贝好, 返回
  • AIO:进一步提高了效率,支持真正的异步 I/O,但编程复杂度最高。
  1. 发起读请求
  2. 请求返回,做自己的事情
  3. 数据准好了, 提醒请求方数据可以用了
  • 其他说明
  1. 实际编程中, 使用较多的是bio, 因为其代码简单,易于理解。
  2. nio则一般用于框架核心级别的代码, 编程难度介于BIO和AIO之间, 且有如netty之类的较为成熟的框架面世
  3. aio 是三种IO当中效率最高的, 但是因其编程难度极高, 目前的使用案例较少。而一般开发过程更是很少用到aio.

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

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

相关文章

三级_网络技术_55_应用题

一、 请根据下图所示网络结构回答下列问题。 1.填写RG的路由表项。 目的网络/掩码长度输出端口__________S0&#xff08;直接连接&#xff09;__________S1&#xff08;直接连接&#xff09;__________S0__________S1__________S0__________S1 (2)在不改变路由表项的前提下&…

django学习入门系列之第十点《案例 用户登录》

文章目录 案例 用户登录安全认证django中的隐藏值获得用户账户密码空值 往期回顾 案例 用户登录 安全认证 ​ 如果提交数据后&#xff0c;发现并没有跳转到自己想要的界面&#xff0c;是因为django比Flask多一层 ”安全机制“ 的东西 解决方法&#xff1a; {% csrf_token %…

使用maven搭建微服务框架

徒手搭建cloud 1.认准SpringBoot,SpringCloud,SpringCloudAlibaba版本之间的对用关系 官网给出了声明&#xff1a;https://github.com/alibaba/spring-cloud-alibaba/wiki 2.选择好版本之后 spring bootspring cloudspring cloud alibaba2.3.12.RELEASEHoxton.SR102.2.5.REL…

Ps:工具预设面板

Ps菜单&#xff1a;窗口/工具预设 Window/Tool Presets 工具预设 Tool Presets面板可以为 Photoshop 的图像编辑工作带来极大的便利。 定义好相关的工具预设后&#xff0c;可以直接调用&#xff0c;而不管现在处于什么工具或什么样的参数状态&#xff0c;省去了再次设置参数的麻…

使用 树莓派3B+ 对日本葡萄园进行经济实惠的环境监测

对于 菊岛邦夫—Vineyard Kikushima 而言&#xff0c;Raspberry Pi 生态系统提供了支持和信息&#xff0c;通过基于温度和湿度监测的有针对性的最低限度杀虫剂方案&#xff0c;来提高葡萄的健康产量。 Vineyard Kikushima&#xff1a;http://vykikushima.greater.jp/vineyards…

finalshell 用 root 账号连接 ubuntu

我们平时在操作 linux 系统时&#xff0c;经常需要上传文件&#xff0c;修改文件&#xff0c;普通账号只能通过 vim 等工具修改&#xff0c;诸多不便。为了实现跟 windows 一样&#xff0c;双击直接编辑保存&#xff0c;需要下面步骤。 1. ubuntu 安装 ssh 1.1 安装 SHH 服务…

LuaJit分析(十)luajit自定义修改

通过分析luajit字节码文件格式可知&#xff0c;luajit文件由文件头和原型数组组成&#xff0c;而原型又包括原型头和原型体&#xff0c;文件头中包含了字节码文件的一些关键信息&#xff0c;目前的反编译工具根据标准的luajit2.0文件格式解析文件&#xff0c;如果对字节码文件的…

[Algorithm][综合训练][哈夫曼编码][abb][旋转字符串]详细讲解

目录 1.哈夫曼编码1.题目链接2.算法原理详解 && 代码实现 2.abb1.题目链接2.算法原理详解 && 代码实现 3.旋转字符串1.题目链接2.算法原理详解 && 代码实现 1.哈夫曼编码 1.题目链接 哈夫曼编码 2.算法原理详解 && 代码实现 哈夫曼编码&…

Linux系统应用(3)——编辑器vim

个人内容简介&#xff1a; &#x1f343;个人主页&#xff1a;诉清风2023 &#x1f388;逆转时间的公式&#xff0c;就是珍惜现在ദ്ദി˶&#xff70;̀֊&#xff70;́ ) ✧ 目录 个人内容简介&#xff1a; &#x1f388;逆转时间的公式&#xff0c;就是珍惜现在ദ്ദ…

junit格式报告解析工具

前言 在测试过程中&#xff0c;使用unittest或者pytest等主流框架&#xff0c;都可以生成junit格式的测试报告。当然也可以生成html格式的报告。但是为了自定义自己的测试报告&#xff0c;我开发了一款web网页工具&#xff0c;使用该工具上传junit格式的xml报告&#xff0c;可…

[Algorithm][综合训练][奇数位丢弃][求和][计算字符串的编辑距离]详细讲解

目录 1.奇数位丢弃1.题目链接2.算法原理详解 && 代码实现 2.求和1.题目链接2.算法原理详解 && 代码实现 3.计算字符串的编辑距离1.题目链接2.算法原理详解 && 代码实现 1.奇数位丢弃 1.题目链接 奇数位丢弃 2.算法原理详解 && 代码实现 解法…

丙类谐振功放的工作状态分析

前言&#xff1a;我们之前已经根据导通角θ&#xff0c;将工作状态分成了甲乙丙三类。我们发现丙类的效率最高&#xff0c;所以现在我们专门分析一下丙类工作状态。 1定量分析与定性分析 定量分析&#xff1a;是指分析以数量形式存在着的属性。 定性分析&#xff1a;是指分析…

2024.8.29 C++

作业 仿照string类&#xff0c;实现myString 代码 #include <iostream> #include <cstring> using namespace std; //仿照string完成myString类 class myString {private:char *str; //记录c风格的字符串int size; //记录字符串的实际长度p…

flex实现骰(tou)子点数

文章目录 效果演示分析思路代码实现 效果演示 分析思路 5点需要使用margin进行移动点数。而6点的话&#xff0c;使用align-content: space-between;和 justify-content: space-between;就能实现&#xff0c;不过需要注意的是主轴为侧轴&#xff0c;dot的第二个要给padding才能实…

【功能自动化】自动识别测试用例

1.创建unitWebtours.py 将unitWebtours.py放在test文件夹下 unitWebtours.py 代码实现 from selenium import webdriver from selenium.webdriver.support.select import Select from time import sleep import unittestdriver Noneclass Webtours(unittest.TestCase):clas…

java写入word表格(poi-tl)

1.导入依赖 <!--poi-tl--> <dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.12.0</version> </dependency>2.代码 自己创建模板。放在&#xff08;resource/file&#xff09;…

如何更改 Mac 上 Java 的默认版本?

优质博文&#xff1a;IT-BLOG-CN 第一次运行/usr/libexec/java_home -V将输出类似以下内容&#xff1a; Matching Java Virtual Machines (3): 1.8.0_05, x86_64: "Java SE 8" /Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home 1.6.0_65-b14-4…

那么啥是MySQL集群技术捏

Mysql 集群技术 一 Mysql 在服务器中的部署方法 在企业中90%的服务器操作系统均为Linux 在企业中对于Mysql的安装通常用源码编译的方式来进行 官网&#xff1a;http://www.mysql.com 1.1 在Linux下部署mysql 1.1.1 安装依赖性&#xff1a; [rootmysql1 ~]# yum install c…

Windows通过网线连接开发板共享网络

Windows端 打开更开适配器选项右键WLAN–属性–共享 右键以太网–属性–Internet协议版本4(TCP/IPv4) 记住IP地址 开发板端 查看网卡 ifconfig设置IP在同一网段 ifconfig eth0 192.168.137.2 netmask 255.255.255.0设置网关 route add default gw 192.168.137.1配置DNS su…

Python-MNE-源空间和正模型03:自动源配准的方法

这个例子展示了如何使用coregistration函数通过脚本自动完成MEG-MRI的coregistration。一般情况下&#xff0c;该方法的结果与人工共配准的结果是一致的。 **但一定要注意&#xff1a;**协同配准的质量在很大程度上取决于受试者准备过程中收集的头形点(HSP)的质量和t1加权MRI的…