java 的三种IO模型(BIO、NIO、AIO)

java 的三种IO模型(BIO、NIO、AIO)

    • 一、BIO 阻塞式 IO(Blocking IO)
      • 1.1、BIO 工作机制
      • 1.2、BIO 实现单发单收
      • 1.3、BIO 实现多发多收
      • 1.4、BIO 实现客户端服务端多对一
      • 1.5、BIO 模式下的端口转发思想
    • 二、NIO 同步非阻塞式 IO(Non-blocking IO)
      • 2.1、NIO 3个核心组件(缓冲区、通道、选择器)
      • 2.2、NIO 主要特性
      • 2.3、NIO 与 BIO 的对比
      • 2.4、Buffer 常用子类
      • 2.5、Buffer 重要属性
    • 三、AIO 异步式 IO(Asynchronous IO)
      • 3.1、AIO 核心组件(异步通道、完成处理器)


一、BIO 阻塞式 IO(Blocking IO)

每个客户端连接都会在一个独立的线程中处理,并且这个线程在处理 IO 操作时会阻塞,直到操作完成。

  • 每个连接都需要一个独立的线程,连接数较多时,会消耗大量的内存和 CPU 资源
  • 线程在处理 IO 操作时会阻塞

1.1、BIO 工作机制

  • 客户端通过 Socket 对象与服务端建立连接,从 Socket 中得到字节输入流或输出流进行数据读写。
  • 服务端通过 ServerSocket 注册端口,调用 accept 方法监听客户端 Socket 请求,从 Socket 中得到字节输入流或输出流进行数据读写。

1.2、BIO 实现单发单收

客户端:

public static void main(String[] args) {
	Socket socket = null;
	try {
		//与服务端连接
		socket = new Socket("127.0.0.1", 5000);
		//从 socket 管道中获取字节输出流
		OutputStream os = socket.getOutputStream();
		//将字节输出流包装为打印流
		PrintStream ps = new PrintStream(os);
		//发一行数据
		ps.println("Hi BIO! 与服务端通信成功");
		ps.flush();
	} catch (IOException e) {
		e.printStackTrace();
	}
}

服务端:

 public static void main(String[] args) {
 	System.out.println("===服务端启动===");
 	ServerSocket serverSocket = null;
 	try {
 		//注册端口
		serverSocket = new ServerSocket(5000);
		//监听客户端请求
		Socket socket = serverSocket.accept();
		//从 socket 管道中获取字节输入流
		InputStream is = socket.getInputStream();
		//将字节输入流包装为缓冲字符输入流
		BufferedReader br = new BufferedReader(new InputStreamReader(is));
		
		String msg;
		//读一行数据
		if ((msg = br.readLine()) != null) {
			System.out.println("服务端接收客户端信息为:" + msg);
		}
	}catch (Exception e){
		System.out.println(e.getMessage());
	}
}

1.3、BIO 实现多发多收

客户端:

public static void main(String[] args) {
    try {
        Socket socket = new Socket("localhost",9988);
        OutputStream os = socket.getOutputStream();
        PrintStream ps = new PrintStream(os);
        Scanner scanner = new Scanner(System.in);
        while (true){
            System.out.println("请输入:");
            String input = scanner.nextLine();
            ps.println(input);
            ps.flush();
        }
     } catch (IOException e) {
     	e.printStackTrace();
     }
}

服务端:

public static void main(String[] args) {
    System.out.println("===服务端启动===");
    try {
         ServerSocket ss = new ServerSocket(9988);
         Socket socket = ss.accept();
         InputStream is = socket.getInputStream();
         BufferedReader br = new BufferedReader(new InputStreamReader(is));
         String message;
         while ((message = br.readLine()) != null){
             System.out.println("服务端接收客户端信息为:" + message);
         }
     } catch (IOException e) {
		e.printStackTrace();
     }
}

1.4、BIO 实现客户端服务端多对一

服务端:

public void listen() throws IOException {
	ServerSocket serverSocket = null;
	try {
		log.info("服务启动监听");
		serverSocket = new ServerSocket(9988);
		//循环接收到客户端的连接
		while (true) {
			Socket socket = serverSocket.accept();
			//得到连接后,开启一个线程处理连接
			handleSocket(socket);
		}
	} finally {
		if(serverSocket != null){
			serverSocket.close();
		}
	}
}

private void handleSocket(Socket socket) {
	HandleSocket socketHandle = new HandleSocket(socket);
	new Thread(socketHandle).start();
}
public void run() {
	BufferedInputStream bufferedInputStream = null;
	BufferedOutputStream bufferedOutputStream  = null;
	try {
		bufferedInputStream = new BufferedInputStream(socket.getInputStream());
		byte[] bytes = new byte[1024];
		int len ;
		if ((len = bufferedInputStream.read(bytes)) > -1) {
			String result = new String(bytes,0,len);
            System.out.println("本次接收到的结果:" + result);
        }
        System.out.println("回复信息给客户端:");
        bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream());
        String outString = Thread.currentThread().getName() + "接收到了";
        bufferedOutputStream.write(outString.getBytes());
        bufferedOutputStream.flush();
	} catch (IOException e) {
		System.out.println("处理异常:" + e.getMessage());
	} finally {
		try {
			if (bufferedInputStream != null) {
				bufferedInputStream.close();
			}
			if (bufferedOutputStream != null) {
				bufferedOutputStream.close();
			}
		}catch (IOException e){
			System.out.println("关闭流异常:" + e.getMessage());
		}
	}
}

客户端:

public void start() throws IOException {
	Socket socket = new Socket("127.0.0.1", 8081);
	String msg = "Hi,This is the BioClient";
	BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream());
	byte[] bytes = msg.getBytes();
	bufferedOutputStream.write(bytes);
	bufferedOutputStream.flush();
	System.out.println("发送完毕");
	BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream());
	byte[] inBytes = new byte[1024];
	int len;
	if ((len = bufferedInputStream.read(inBytes)) != -1) {
		String result = new String(inBytes, 0, len);
		System.out.println("接收到的消息="+result);
	}
	bufferedOutputStream.close();
	bufferedInputStream.close();
	socket.close();
}

1.5、BIO 模式下的端口转发思想

一个客户端的消息经由服务端发送给所有的客户端,实现群聊功能。
在这里插入图片描述

public class Server {

    // 定义一个静态集合
    public static List<Socket> allSocketOnLine = new ArrayList();

    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(9999);
            while (true){
                Socket socket = ss.accept();
                // 把登录的客户端socket存入到一个在线的集合中去
                allSocketOnLine.add(socket);
                // 为当前登录成功的socket分配一个独立的线程来处理
                new ServerReaderThread(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

public class ServerReaderThread extends Thread{

    private Socket socket;
    
    public ServerReaderThread(Socket socket){
        this.socket = socket;
    }
    
    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String msg;
            while ((msg = br.readLine()) != null) {
                // 发送给所有的在线socket
                sendMsgToAllClient(msg);
            }
        } catch (Exception e) {
            System.out.println("有人下线了");
            Server.allSocketOnLine.remove(socket);
        }

    }

    /**
     * 把当前客户端发来的消息发送给全部的在线socket
     * @param msg
     */
    private void sendMsgToAllClient(String msg) throws IOException {
        for (Socket sk : Server.allSocketOnLine) {
            PrintWriter pw = new PrintWriter(sk.getOutputStream());
            pw.println(msg);
            pw.flush();
        }
    }
    
}

二、NIO 同步非阻塞式 IO(Non-blocking IO)

允许线程在等待IO操作完成期间可以继续执行其他任务。

2.1、NIO 3个核心组件(缓冲区、通道、选择器)

缓冲区(Buffer):用于存储数据的对象。数据从通道读取到缓冲区,或者从缓冲区写入到通道。

通道(Channel):既可以从通道中读取数据,又可以写数据到通道

选择器(Selector):同时管理多个通道,通过注册通道的事件(如连接就绪、读就绪、写就绪),使用单个线程就能处理多个通道,从而管理多个网络连接,提高了效率。
在这里插入图片描述

2.2、NIO 主要特性

  • 非阻塞I/O:允许线程在等待IO操作完成期间可以继续执行其他任务
  • IO多路复用:通过选择器,NIO允许多个通道共用一个线程进行管理,减少了线程的资源消耗。
  • 异步IO操作:可以在通道上注册事件和回调函数,实现非阻塞的IO操作
  • 内存映射文件:将文件的一部分或全部直接映射到内存中,这样可以像访问内存一样访问文件,提高了文件处理的效率。
  • 文件锁定:允许对文件的部分或全部进行锁定,从而控制对文件的并发访问。

2.3、NIO 与 BIO 的对比

  • 面向流与面向缓冲:BIO 是面向流的,每次从流中读一个或多个字节,直至读取所有字节;而 NIO 是面向缓冲区的。
  • 阻塞与非阻塞:BIO 的流是阻塞的,当一个线程调用 read()write() 时,该线程被阻塞,直到有一些数据被读取或数据完全写入;而 NIO 是非阻塞的,一个线程从某通道发送请求读取数据,它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。
  • 线程开销:BIO 为每个客户端连接创建一个线程,在大量并发连接的情况下会带来巨大的线程开销;NIO 通过选择器实现 I/O多路复用,在一个线程中处理多个通道,减少了线程开销。

2.4、Buffer 常用子类

  • ByteBuffer:用于存储字节数据;
  • CharBuffer:用于存储字符数据;
  • ShortBuffer:用于存储Short类型数据;
  • IntBuffer:用于存储Int类型数据;
  • LongBuffer:用于存储Long类型数据;
  • FloatBuffer:用于存储Float类型数据;
  • DoubleBuffer:用于存储Double类型数据;

2.5、Buffer 重要属性

  • capacity(容量):表示 Buffer 所占的内存大小,不能为负且创建后不能更改
  • limit(限制):表示 Buffer 中可以操作数据的大小,不能为负且不能大于 capacity
    写模式下,表示最多能往 Buffer 里写多少数据,即 limit 等于 capacity
    读模式下,表示最多能读到多少数据,即已写入的所有数据
  • position(位置):表示下一个要读取或写入的数据的索引
    缓冲区位置不能为负,且不能大于其限制
    初始 position 值为 0,最大为 capacity – 1。当一个 byte、long 等数据写到 Buffer 后, position 会向前移动到下一个可插入数据的 Buffer 单元
  • mark(标记):表示记录当前 position 的位置,可通过 reset() 恢复到 mark 的位置

三、AIO 异步式 IO(Asynchronous IO)

异步式IO操作不会阻塞线程,而是交由操作系统处理。完成后,操作系统会通知应用程序,或者应用程序主动查询完成状态。使线程在等待IO完成的同时可以执行其他任务,提高了系统的并发性能。

3.1、AIO 核心组件(异步通道、完成处理器)

  1. 异步通道(Asynchronous Channel):AIO 中进行I/O操作的基础设施。
    AIO提供了多种异步通道:
    AsynchronousSocketChannel(异步套接字通道,支持面向连接的网络通信)AsynchronousServerSocketChannel(异步服务器套接字通道,支持异步服务器端套接字通信)AsynchronousFileChannel(异步文件通道,支持异步文件读写操作)

  2. 完成处理器(Completion Handler):用于在I/O操作完成后处理结果的回调接口。
    完成处理器包含两个方法:
    completed(V result, A attachment) 在I/O操作成功完成时调用;
    failed(Throwable exc, A attachment) 在I/O操作失败时调用。

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

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

相关文章

Android15车载音频之Virtualbox中QACT实时调试(八十八)

简介: CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+…

Pikachu- Over Permission-垂直越权

以admin 账号登陆&#xff0c;添加一个用户&#xff1b; 把添加用户的这个请求发送到 repeater&#xff1b; 退出admin&#xff0c;使用普通用户pikachu登陆&#xff1b; 只有查看权限&#xff1b; 使用pikachu 用户的认证信息&#xff0c;替换repeater处管理员创建用户请求的…

六、索引的数据结构

文章目录 1. 为什么使用索引2. 索引及其优缺点2.1 索引概述2.2 优点2.3 缺点3. InnoDB中索引的推演3.1 索引之前的查找3.1.1 在一个页中的查找3.1.2 在很多页中查找3.2 设计索引3.2.1 一个简单的索引设计方案3.2.2 InnoDB中的索引方案3.3 常见索引概念3.3.1 聚簇索引3.3.2 二级…

UDP协议【网络】

文章目录 UDP协议格式 UDP协议格式 16位源端口号&#xff1a;表示数据从哪里来。16位目的端口号&#xff1a;表示数据要到哪里去。16位UDP长度&#xff1a;表示整个数据报&#xff08;UDP首部UDP数据&#xff09;的长度。16位UDP检验和&#xff1a;如果UDP报文的检验和出错&…

centos一些常用命令

文章目录 查看磁盘信息使用 df 命令使用 du 命令 查看磁盘信息 使用 df 命令 df&#xff08;disk free&#xff09;命令用于显示文件系统的磁盘空间占用情况。 查看所有挂载点的磁盘使用情况&#xff1a; df -h选项说明&#xff1a; -h 参数表示以人类可读的格式&#xff0…

Windows下Jenkins控制台中文乱码

问题描述 问题情况如下图&#xff1a; 环境信息 Windows 11 家庭中文版java 21.0.4 2024-07-16 LTSJenkins 2.452.3 解决方法 增加系统JAVA_TOOL_OPTIONS&#xff0c;并设置值为-Dfile.encodingGBK。 打开设置方法&#xff1a;桌面上右键点击“此电脑”图标&#xff0c;选…

【黑马点评】使用RabbitMQ实现消息队列——3.使用Jmeter压力测试,导入批量token,测试异步秒杀下单

3 批量获取用户token&#xff0c;使用jmeter压力测试 3 批量获取用户token&#xff0c;使用jmeter压力测试3.1 需求3.2 实现3.2.1 环境配置3.2.2 修改登录接口UserController和实现类3.2.3 测试类 3.3 使用jmeter进行测试3.4 测试结果3.5 将用户登录逻辑修改回去 3 批量获取用户…

力扣16~20题

题16&#xff08;中等&#xff09;&#xff1a; 思路&#xff1a; 双指针法&#xff0c;和15题差不多&#xff0c;就是要排除了&#xff0c;如果total<target则排除了更小的&#xff08;left右移&#xff09;&#xff0c;如果total>target则排除了更大的&#xff08;rig…

三绕组单相电容电动机的瞬态分析(2)定子三角形接法时起动过程及稳态性能分析

1. 引言 2. 定子接三绕组单相电容电动机的数学模型 3.最佳移相电容计算 4. 仿真分析实例 5. 总结 6. 参考文献 1. 引言 目前,三相供电系统在全世界范围内已是非常普遍了。但是,由于架设输电线成本高,受输、配电系统的限制,城市居民用电和大多数农村及边远地区的用电仍…

JavaWeb——Vue路由(概述、介绍、使用、解决bug)

目录 概述 介绍 使用 解决bug 概述 员工管理的页面已经制作完成。其他页面的制作方式一致。 项目中准备了部门管理的页面组件 DeptView &#xff0c;这样就有了员工管理和部门管理两个页面组件。 在系统中&#xff0c;要实现点击部门管理菜单展示部门管理组件&#xff0c…

线性代数入门指南

在数学的广袤领域中&#xff0c;线性代数犹如一座神秘而又充满魅力的殿堂&#xff0c;等待着初学者去探索。当你踏入线性代数的大门&#xff0c;便开启了一段充满挑战与惊喜的知识之旅。 线性代数是什么呢&#xff1f;简单来说&#xff0c;它是一门研究线性方程组、向量空间、线…

Android Automotive(一)

目录 什么是Android Automotive Android Automotive & Android Android Automotive 与 Android Auto 什么是Android Automotive Android Automotive 是一个基础的 Android 平台&#xff0c;它能够运行预装的车载信息娱乐系统&#xff08;IVI&#xff09;应用程序&#…

分治算法(3)_快速选择_数组中的第K个最大元素

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 分治算法(3)_快速排序_数组中的第K个最大元素 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#…

51单片机的自动制冷系统【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块温度传感器继电器LED、按键和蜂鸣器等模块构成。适用于车载便携式自动制冷系统、冰箱制冷、温度控制等相似项目。 可实现功能: 1、LCD1602实时显示当前温度 2、温度传感器DS18B20采集温度 3、按键可设置温度的阈…

双向数据库迁移工具:轻松实现 MySQL 与 SQLite 数据互导

项目概述与作用 该项目的核心是实现 MySQL 和 SQLite 两种数据库之间的数据迁移工具。它能够轻松地将 MySQL 数据库中的数据导出为 SQLite 数据库文件&#xff0c;反过来也可以将 SQLite 数据库中的数据上传到 MySQL 数据库中。这个双向迁移工具非常适用于&#xff1a; 数据库备…

日期类的实现(C++)

个人主页&#xff1a;Jason_from_China-CSDN博客 所属栏目&#xff1a;C系统性学习_Jason_from_China的博客-CSDN博客 所属栏目&#xff1a;C知识点的补充_Jason_from_China的博客-CSDN博客 前言 日期类是六个成员函数学习的总结和拓展&#xff0c;是实践的体现 创建文件 构造函…

[C#]使用onnxruntime部署yolov11-onnx实例分割模型

【官方框架地址】 https://github.com/ultralytics/ultralytics.git 【算法介绍】 在C#中使用ONNX Runtime部署YOLOv11-ONNX实例分割模型&#xff0c;涉及到模型的加载、数据预处理、模型推理和后处理几个关键步骤。 首先&#xff0c;需要确保已经安装了ONNX Runtime的NuGe…

whisper 实现语音识别 ASR - python 实现

语音识别&#xff08;Speech Recognition&#xff09;&#xff0c;同时称为自动语音识别&#xff08;英语&#xff1a;Automatic Speech Recognition, ASR&#xff09;&#xff0c;将语音音频转换为文字的技术。 whisper是一个通用的语音识别模型&#xff0c;由OpenAI公司开发。…

【Spring】“请求“ 之后端传参重命名,传递数组、集合,@PathVariable,@RequestPart

1. 后端传参重命名&#xff08;后端参数映射&#xff09; 某些特殊情况下&#xff0c;前端传递的参数 key 和我们后端接收的 key 可以不一致&#xff0c;比如前端传了一个 time 给后端&#xff0c;而后端是使用 createtime 字段来接收的&#xff0c;这样就会出现参数接收不到的…

【新人系列】Python 入门(一):介绍及环境搭建

✍ 个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4dd; 专栏地址&#xff1a;https://blog.csdn.net/newin2020/category_12801353.html &#x1f4e3; 专栏定位&#xff1a;为 0 基础刚入门 Python 的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们…