Java BIO详解

news2025/2/5 5:09:51

一、简介

1.1 BIO概述

BIO(Blocking I/O),即同步阻塞IO(传统IO)。

 BIO 全称是 Blocking IO,同步阻塞式IO,是JDK1.4之前的传统IO模型,就是传统的 java.io 包下面的代码实现。

服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如下图所示:

在 BIO 模型下,应用程序会在进行 I/O 操作时阻塞当前线程,直到 I/O 操作完成。例如,执行一个读取操作时,线程会等待,直到数据从磁盘或网络中完全读取完成。在这个过程中,线程不能做其他任务,必须等待 I/O 操作的结果。

BIO 模型的特点

  • 同步阻塞:
    • 当线程进行 I/O 操作时,它会被阻塞,直到操作完成。
    • 阻塞操作通常会导致 CPU 的浪费,因为线程在等待 I/O 时并没有进行其他有用的工作。
  • 一个连接一个线程:
    • 每个客户端请求都会创建一个新的线程,每个线程对应一个 I/O 操作。
    • 当并发连接数很多时,系统可能会因为线程数过多而导致性能瓶颈。
  • 适用于连接数较少的场景:BIO 更适用于连接数较少、请求不频繁的应用场景,如一些小型应用或传统的阻塞式通信。
  • 容易实现:相对于其他 I/O 模型(如 NIO 和 AIO),BIO 的实现比较简单,应用开发人员只需要关心 I/O 操作,不需要处理复杂的事件驱动机制。

BIO 的缺点:

  • 线程资源浪费:每个连接都会对应一个独立的线程,当有大量并发连接时,会导致系统开销巨大,因为操作系统会为每个线程分配资源(如内存、栈空间等)。如果并发请求量很大,线程上下文切换开销也会非常高。
  • 不适合高并发:BIO 模型非常依赖操作系统线程,线程数过多时容易造成系统性能下降。特别是在高并发的情况下,线程的创建和销毁频繁,容易耗尽系统资源。
  • 效率低:在 I/O 操作过程中,线程被阻塞,无法处理其他请求,导致 CPU 的浪费。即使没有数据可读或可写,线程依然会等待,直到 I/O 完成。

1.2 IO流概述

IO流是基于流的概念,它将数据的输入和输出看作是一个连续的流。数据从一个地方流向另一个地方,流的方向可以是输入(读取数据)或输出(写入数据)。

IO流的原理是通过流的管道将数据从源头传输到目标地。源头可以是文件、网络连接、内存等,而目标地可以是文件、数据库、网络等。IO流提供了一组丰富的类和方法来实现不同类型的输入和输出操作。

IO流主要用于处理输入和输出操作,适用于以下场景:

  • 文件读写:通过IO流可以读取和写入文件中的数据,如读取配置文件、写入日志等。
  • 网络通信:通过IO流可以进行网络数据的传输和接收,如Socket通信、HTTP请求等。
  • 数据库操作:通过IO流可以将数据读取到内存中,或将内存中的数据写入到数据库中。
  • 文本处理:通过IO流可以读取和写入文本文件,进行文本处理和操作。

1.2.1 IO流分类

Java中的IO流可以按照数据的类型和流的方向进行分类:

  • 按数据类型分类
    • 字节流(Byte Stream):以字节为单位读写数据,适用于处理二进制数据,如图像、音频、视频等。常见的字节流类有InputStream和OutputStream。
    • 字符流(Character Stream):以字符为单位读写数据,适用于处理文本数据。字符流会自动进行字符编码和解码,可以处理多国语言字符。常见的字符流类有Reader和Writer。
  • 按流的方向分类
    • 输入流(Input Stream):用于读取数据。输入流从数据源读取数据,如文件、网络连接等。常见的输入流类有FileInputStream、ByteArrayInputStream、SocketInputStream等。
    • 输出流(Output Stream):用于写入数据。输出流将数据写入到目标地,如文件、数据库、网络等。常见的输出流类有FileOutputStream、ByteArrayOutputStream、SocketOutputStream等。

1.2.1.1 字符流
  • 只用来处理文本数据 ;
  • 数据最常见的表现形式是文件,字符流用来操作文件的子类一般是 FileReader 和 FileWriter 。

字符流读写文件注意事项:

  • 写入文件必须要用 flush() 刷新 ;
  • 用完流记得要关闭流 ;
  • 使用流对象要抛出IO异常 ;
  • 定义文件路径时,可以用"/"或者"\" ;
  • 在创建一个文件时,如果目录下有同名文件将被覆盖 ;
  • 在读取文件时,必须保证该文件已存在,否则抛出异常 ;
  • 字符流的缓冲区 ;
  • 缓冲区的出现是为了提高流的操作效率而出现的 ;
  • 需要被提高效率的流作为参数传递给缓冲区的构造函数 ;
  • 在缓冲区中封装了一个数组,存入数据后一次取出 。
1.2.1.2 字节流
  • 用来处理媒体数据 。

字节流读写文件注意事项:

  • 字节流和字符流的基本操作是相同的,但是想要操作媒体流就需要用到字节流 ;
  • 字节流因为操作的是字节,所以可以用来操作媒体文件(媒体文件也是以字节存储的);
  • 输入流(InputStream)、输出流(OutputStream);
  • 字节流操作可以不用刷新流操作 ;
  • InputStream特有方法:int available()(返回文件中的字节数);
  • 字节流的缓冲区 ;
  • 字节流缓冲区跟字符流缓冲区一样,也是为了提高效率 。

1.2.2 常用IO流

1.2.2.1 字节输入流类
  • InputStream:用于从输入源读取字节数据的抽象类。
  • FileInputStream:从文件中读取字节数据的类。
  • ByteArrayInputStream:从字节数组中读取字节数据的类。
  • BufferedInputStream:提供缓冲功能的字节输入流类。
  • DataInputStream:读取基本数据类型的字节输入流类。
try (InputStream is = new FileInputStream("input.txt");
     BufferedInputStream bis = new BufferedInputStream(is)) {

    int data;
    while ((data = bis.read()) != -1) {
        System.out.print((char) data);
    }
} catch (IOException e) {
    e.printStackTrace();
}
1.2.2.2 字符输入流类
  • Reader:用于从输入源读取字符数据的抽象类。
  • FileReader:从文件中读取字符数据的类。
  • BufferedReader:提供缓冲功能的字符输入流类。
  • InputStreamReader:将字节流转换为字符流的类。
try (Reader reader = new FileReader("input.txt");
     BufferedReader br = new BufferedReader(reader)) {

    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}
1.2.2.3 字节输出流类
  • OutputStream:用于向输出目标写入字节数据的抽象类。
  • FileOutputStream:将字节数据写入文件的类。
  • ByteArrayOutputStream:将字节数据写入字节数组的类。
  • BufferedOutputStream:提供缓冲功能的字节输出流类。
  • DataOutputStream:将基本数据类型写入输出流的类。
try (OutputStream os = new FileOutputStream("output.txt");
     BufferedOutputStream bos = new BufferedOutputStream(os)) {

    String data = "Hello, World!";
    bos.write(data.getBytes());
} catch (IOException e) {
    e.printStackTrace();
}
1.2.2.4 字符输出流类
  • Writer:用于向输出目标写入字符数据的抽象类。
  • FileWriter:将字符数据写入文件的类。
  • BufferedWriter:提供缓冲功能的字符输出流类。
  • OutputStreamWriter:将字节流转换为字符流的类。
try (Writer writer = new FileWriter("output.txt");
     BufferedWriter bw = new BufferedWriter(writer)) {

    String data = "Hello, World!";
    bw.write(data);
} catch (IOException e) {
    e.printStackTrace();
}

1.2.3 Java Scanner类

Java 5添加了 java.util.Scanner 类,这是一个用于扫描输入文本的新的实用程序。

  1. 关于 nextInt()、next()、nextLine() 的理解 :
  • nextInt() :只能读取数值,若是格式不对,会抛出 java.util.InputMismatchException 异常 ;
  • next() :遇见第一个有效字符(非空格,非换行符)时,开始扫描,当遇见第一个分隔符或结束符(空格或换行符)时,结束扫描,获取扫描到的内容 ;
  • nextLine() :可以扫描到一行内容并作为字符串而被捕获到 。
  1. 关于 hasNext()、hasNextLine()、hasNextxxx() 的理解 :就是为了判断输入行中是否还存在xxx的意思。
  2. 与 delimiter() 有关的方法的理解 :应该是输入内容的分隔符设置,

二、BIO工作机制

客户端:

  • 通过Socket对象请求与服务端建立连接。
  • 从Socket中得到字节输入或者字节输出流进行数据读写操作。

服务端:

  • 通过ServerSocket注册端口。
  • 服务端通过调用accept方法用于监听客户端的Socket请求。
  • 从Socket中得到字节输入或者字节输出流进行数据读写操作。

三、简单编码实现

3.1 服务端

public class BioServer {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(8080);
            System.out.println("Server is listening on port 8080...");

            while (true) {
                // 阻塞等待客户端连接
                Socket clientSocket = serverSocket.accept();
                System.out.println("Client connected: " + clientSocket.getInetAddress());

                // 为每个客户端请求创建一个新线程进行处理
                new Thread(new ClientHandler(clientSocket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(serverSocket != null){
                    serverSocket.close();
                }
            } catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

class ClientHandler implements Runnable {
    private Socket socket;

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

    @Override
    public void run() {
        BufferedInputStream bufferedInputStream = null;
        BufferedOutputStream bufferedOutputStream  = null;
        try {
            bufferedInputStream = new BufferedInputStream(socket.getInputStream());;
            bufferedOutputStream  = new BufferedOutputStream(socket.getOutputStream());

            System.out.println("收到来自客户端的消息:");
            byte[] bytes = new byte[1024]; //创建字节数组,存储临时读取的数据
            int len ; //记录数据读取的长度
            if ((len = bufferedInputStream.read(bytes)) > -1) { //长度为-1则读取完毕
                String result = new String(bytes,0,len);
                System.out.println(result);
            }

            String outString = "服务端收到,这里是线程" + Thread.currentThread().getName();
            System.out.println("回复信息给客户端: " + outString);
            bufferedOutputStream.write(outString.getBytes());
            bufferedOutputStream.flush();
            System.out.println("回复完成========");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("关闭数据流========");
            try {
                if (bufferedInputStream != null) {
                    bufferedInputStream.close();
                }
                if (bufferedOutputStream != null) {
                    bufferedOutputStream.close();
                }
            } catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

工作过程:

  • 服务器通过 ServerSocket 对象监听 8080 端口,等待客户端连接。
  • 每当有一个客户端连接到服务器时,serverSocket.accept() 会阻塞当前线程,直到有客户端连接进来。
  • 然后,服务器会为每个客户端连接创建一个新线程来处理该客户端的请求。
  • 线程通过 BufferedInputStream 读取客户端发送的数据,并通过 BufferedOutputStream 回复客户端。

3.2 客户端

public class BioClient {

    public static void start() throws IOException {
        Socket socket = new Socket("127.0.0.1", 8080);
        String msg = "Hi,This is the BioClient";
        byte[] msgBytes = msg.getBytes();
        //1.拿到输出流
        //2.对输出流进行处理
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream());
        //3.输出msg
        bufferedOutputStream.write(msgBytes);
        bufferedOutputStream.flush();
        System.out.println("客户端发送消息完毕: " + msg);
        System.out.println("客户端开始接收到消息==========");
        //4.对输入流进行处理
        BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream());
        System.out.println("客户端接收到的消息:");
        byte[] inBytes = new byte[1024];
        int len;
        //5.读取输入流
        if ((len = bufferedInputStream.read(inBytes)) != -1) {
            String result = new String(inBytes, 0, len);
            System.out.println(result);
        }
        //6.关闭输入输出流
        bufferedOutputStream.close();
        bufferedInputStream.close();
        socket.close();
    }

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

工作过程:

  • 与服务端建立连接
  • 发送消息给服务端
  • 接收服务端返回的消息

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

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

相关文章

Haproxy+keepalived高可用集群,haproxy宕机的解决方案

Haproxykeepalived高可用集群,允许keepalived宕机,允许后端真实服务器宕机,但是不允许haproxy宕机, 所以下面就是解决方案 keepalived配置高可用检测脚本 ,master和backup都要添加 配置脚本 # vim /etc/keepalived…

98,【6】 buuctf web [ISITDTU 2019]EasyPHP

进入靶场 代码 <?php // 高亮显示当前 PHP 文件的源代码&#xff0c;通常用于调试或展示代码&#xff0c;方便用户查看代码逻辑 highlight_file(__FILE__);// 从 GET 请求中获取名为 _ 的参数值&#xff0c;并赋值给变量 $_ // 符号用于抑制可能出现的错误信息&#xff…

九. Redis 持久化-RDB(详细讲解说明,一个配置一个说明分析,步步讲解到位)

九. Redis 持久化-RDB(详细讲解说明&#xff0c;一个配置一个说明分析&#xff0c;步步讲解到位) 文章目录 九. Redis 持久化-RDB(详细讲解说明&#xff0c;一个配置一个说明分析&#xff0c;步步讲解到位)1. RDB 概述2. RDB 持久化执行流程3. RDB 的详细配置4. RDB 备份&恢…

小程序越来越智能化,作为设计师要如何进行创新设计

一、用户体验至上 &#xff08;一&#xff09;简洁高效的界面设计 小程序的特点之一是轻便快捷&#xff0c;用户期望能够在最短的时间内找到所需功能并完成操作。因此&#xff0c;设计师应致力于打造简洁高效的界面。避免过多的装饰元素和复杂的布局&#xff0c;采用清晰的导航…

(done) MIT6.S081 2023 学习笔记 (Day7: LAB6 Multithreading)

网页&#xff1a;https://pdos.csail.mit.edu/6.S081/2023/labs/thread.html (任务1教会了你如何用 C 语言调用汇编&#xff0c;编译后链接即可) 任务1&#xff1a;Uthread: switching between threads (完成) 在这个练习中&#xff0c;你将设计一个用户级线程系统中的上下文切…

JVM执行流程与架构(对应不同版本JDK)

直接上图&#xff08;对应JDK8以及以后的HotSpot&#xff09; 这里主要区分说明一下 方法区于 字符串常量池 的位置更迭&#xff1a; 方法区 JDK7 以及之前的版本将方法区存放在堆区域中的 永久代空间&#xff0c;堆的大小由虚拟机参数来控制。 JDK8 以及之后的版本将方法…

基于开源AI智能名片2 + 1链动模式S2B2C商城小程序视角下的个人IP人设构建研究

摘要&#xff1a;本文深入探讨在开源AI智能名片2 1链动模式S2B2C商城小程序的应用场景下&#xff0c;个人IP人设构建的理论与实践。通过剖析个人IP人设定义中的“诉求”“特质”“可感知”三要素&#xff0c;结合该小程序特点&#xff0c;阐述其对个人IP打造的影响与推动作用&…

刷题汇总一览

文章目录 贪心动态规划数据结构滑动窗口与双指针前缀和动态规划 本题单设计力扣、牛客等多个刷题网站 贪心 贪心后悔 徒步旅行中的补给问题 LCP 30.魔塔游戏 题目使用到的思想解题分析徒步旅行中的补给问题每次我们都加入当前补给点的k个选择&#xff0c;同时进行升序排序&am…

Jupyter Lab的使用

Lab与Notebook的区别: Jupyter Lab和Jupyter notebook有什么区别&#xff0c;这里找到一篇博客不过我没细看&#xff0c; Jupyter Lab和Jupyter Notebook的区别 - codersgl - 博客园 使用起来Lab就是一个更齐全、功能更高级的notebook&#xff0c; 启用滚动输出: 有时候一个…

SpringBoot中关于knife4j 中的一些相关注解

1、效果图 对比可以明显的看到加了注解与没有加注解所表现出来的效果不同&#xff08;加了注解的更加明了清晰&#xff09; 2、实现效果 Tag注解‌用于为测试方法或测试类添加标签&#xff0c;以便在执行测试时根据标签进行过滤。使用Tag注解可以更灵活地控制测试的执行&#…

知识管理系统助力企业信息共享与创新思维的全面提升研究

内容概要 知识管理系统的引入极大地改变了企业内部的信息流程与创新机制。通过有效整合与管理组织内的知识资源&#xff0c;这些系统不仅降低了信息孤岛的现象&#xff0c;还提升了员工之间的协作能力。企业在信息共享方面&#xff0c;通过知识管理系统构建了一个透明、高效的…

LLM - 基于LM Studio本地部署DeepSeek-R1的蒸馏量化模型

文章目录 前言开发环境快速开始LM Studio简单设置模型下载开始对话 模型选择常见错误最后 前言 目前&#xff0c;受限于设备性能&#xff0c;在本地部署的基本都是DeepSeek-R1的蒸馏量化模型&#xff0c;这些蒸馏量化模型的表现可能并没有你想象的那么好。绝大部分人并不需要本…

本地部署 DeepSeek-R1:简单易上手,AI 随时可用!

&#x1f3af; 先看看本地部署的运行效果 为了测试本地部署的 DeepSeek-R1 是否真的够强&#xff0c;我们随便问了一道经典的“鸡兔同笼”问题&#xff0c;考察它的推理能力。 &#x1f4cc; 问题示例&#xff1a; 笼子里有鸡和兔&#xff0c;总共有 35 只头&#xff0c;94 只…

对象的实例化、内存布局与访问定位

一、创建对象的方式 二、创建对象的步骤: 一、判断对象对应的类是否加载、链接、初始化: 虚拟机遇到一条new指令&#xff0c;首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用&#xff0c;并且检查这个符号引用代表的类是否已经被加载、解析和初始化…

OpenAI推出Deep Research带给我们怎样的启示

OpenAI 又发新产品了&#xff0c;这次是面向深度研究领域的智能体产品 ——「Deep Research」&#xff0c;貌似被逼无奈的节奏… 在技术方面&#xff0c;Deep Research搭载了优化后o3模型并通过端到端强化学习在多个领域的复杂浏览和推理任务上进行了训练。因没有更多的技术暴露…

K8S学习笔记-------1.安装部署K8S集群环境

1.修改为root权限 #sudo su 2.修改主机名 #hostnamectl set-hostname k8s-master01 3.查看网络地址 sudo nano /etc/netplan/01-netcfg.yaml4.使网络配置修改生效 sudo netplan apply5.修改UUID&#xff08;某些虚拟机系统&#xff0c;需要设置才能生成UUID&#xff09;#…

【基于SprintBoot+Mybatis+Mysql】电脑商城项目之用户登录

&#x1f9f8;安清h&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;【Spring篇】【计算机网络】【Mybatis篇】 &#x1f6a6;作者简介&#xff1a;一个有趣爱睡觉的intp&#xff0c;期待和更多人分享自己所学知识的真诚大学生。 目录 &#x1f3af;1.登录-持久层 &…

【Deep Seek本地化部署】模型实测:规划求解python代码

目录 前言 一、实测 1、整数规划问题 2、非线性规划问题 二、代码正确性验证 1、整数规划问题代码验证 2、非线性规划问题代码验证 三、结果正确性验证 1、整数规划问题结果正确性验证 2、非线性规划问题正确性验证 四、整数规划问题示例 后记 前言 模型&#xff…

【游戏设计原理】98 - 时间膨胀

从上文中&#xff0c;我们可以得到以下几个启示&#xff1a; 游戏设计的核心目标是让玩家感到“时间飞逝” 游戏的成功与否&#xff0c;往往取决于玩家的沉浸感。如果玩家能够完全投入游戏并感受到时间飞逝&#xff0c;说明游戏设计在玩法、挑战、叙事等方面达到了吸引人的平衡…

C语言基础系列【1】第一个C程序:Hello, World!

C语言的历史与特点 历史背景 C语言起源于20世纪70年代&#xff0c;最初是由美国贝尔实验室的Dennis Ritchie和Ken Thompson为了开发UNIX操作系统而设计的一种编程语言。在UNIX系统的开发过程中&#xff0c;他们发现原有的B语言&#xff08;由Thompson设计&#xff09;在功能和…