当前位置: 首页 > article >正文

Java BIO详解

一、简介

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.kler.cn/a/533009.html

相关文章:

  • Baklib探讨如何通过内容中台提升组织敏捷性与市场竞争力
  • 机器学习中的关键概念:通过SKlearn的MNIST实验深入理解
  • Starrocks 对比 Clickhouse
  • 【B站保姆级视频教程:Jetson配置YOLOv11环境(六)PyTorchTorchvision安装】
  • 【数据分析】案例04:豆瓣电影Top250的数据分析与Web网页可视化(numpy+pandas+matplotlib+flask)
  • 深度学习之“线性代数”
  • Assembly语言的正则表达式
  • 【MySQL】常用语句
  • PHP安全防护:深度解析htmlspecialchars绕过与防御策略
  • 版本控制的重要性及 Git 入门
  • Linux iostat 命令使用详解
  • Linux 信号机制
  • 【Python深入浅出】Python 开启机器学习之旅:项目实战指南
  • 分库分表技术方案选型
  • Spring理论知识(Ⅴ)——Spring Web模块
  • java-(Oracle)-Oracle,plsqldev,Sql语法,Oracle函数
  • 2.4学习记录
  • Vue 组件化开发指南:父子组件传值、emit、refs、事件总线、Provide/Inject
  • 【AI大模型】DeepSeek API大模型接口实现
  • 深入探讨前端新技术:CSS Container Queries 的应用与实践
  • Meta财报解读:营收超预期,用户增长放缓,AI与元宇宙仍是烧钱重点
  • BUU11 [极客大挑战 2019]Secret File1
  • 结合机器视觉与深度学习的 Python 项目
  • fastDFS简介及应用
  • Node.js与嵌入式开发:打破界限的创新结合
  • Qt网络相关