目录

1.理解IO,BIO,NIO,AIO。

1.1基础:IO(Input/Output,输入输出)

1.2 BIO(Blocking IO,阻塞 IO)

1.3 NIO(Non-blocking IO,非阻塞 IO)

1.4 AIO(Asynchronous IO,异步 IO)

2.流写入与写出(基础版)

2.1流写入( 输入: 吸星大法 从源读取)

核心逻辑:

关键特点:

常用输入流类及场景:

示例:用 "吸星大法" 读取文本文件

2.2流写出( 输出,北冥神功 写到目标 )

核心逻辑:

关键特点:

常用输出流类及场景:

示例:用 "北冥神功" 写入文本文件

2.3输入与输出的 "组合技":数据搬运

示例:组合 "吸星 + 北冥" 实现文件复制

3.java.io常用的类有哪些


1.理解IO,BIO,NIO,AIO。

要理解 IO、BIO、NIO、AIO,需要从IO 模型的角度切入 —— 它们本质是不同场景下数据输入输出的处理方式,核心区别体现在同步 / 异步阻塞 / 非阻塞的特性上。、

简单比喻:

  • BIO:点餐后站在窗口一直等餐(阻塞,啥也干不了)。
  • NIO:点餐后每隔一会儿去窗口问 “好了吗”(非阻塞,期间可干别的)。
  • AIO:点餐后留电话,餐好了商家打电话通知(异步,完全不用管)。

1.1基础:IO(Input/Output,输入输出)

IO 是所有数据交换过程的统称,指计算机与外部设备(如磁盘、网络)或其他系统之间的数据读取(Input)和写入(Output)操作。例如:读取本地文件、接收网络数据、向数据库写入数据等,都属于 IO 操作。

1.2 BIO(Blocking IO,阻塞 IO)

BIO 是同步阻塞 IO模型,是最传统的 IO 处理方式。

核心特点:

  • 阻塞性:当发起 IO 操作(如读 / 写)时,线程会被「挂起」,直到操作完成(如数据读完、写入成功)才能继续执行其他任务。
  • 同步性:IO 操作的发起者(应用线程)必须等待操作结果,不能 “撒手不管”。

举例:

传统的 Java 文件读取(FileInputStream)或 Socket 通信:

// BIO读取文件(阻塞示例)
InputStream in = new FileInputStream("file.txt");
byte[] buffer = new byte[1024];
int len = in.read(buffer); // 线程阻塞,直到读取到数据或结束

如果读取的文件很大或网络数据未到达,线程会一直卡在read()方法,期间无法做任何事。

问题:

  • 并发场景下,每处理一个 IO 任务需占用一个线程(如服务器每接一个客户端连接就新建线程),线程资源有限(JVM 线程数通常上限为几千),高并发时会导致线程耗尽、性能崩溃。
  • 适用场景:连接数少、操作简单的场景(如简单的本地文件处理)。

1.3 NIO(Non-blocking IO,非阻塞 IO)

NIO 是同步非阻塞 IO模型,JDK 1.4 引入,用于解决 BIO 的高并发瓶颈。

核心特点:

  • 非阻塞性:IO 操作发起后,若数据未就绪,线程不会阻塞,而是立即返回(通常返回 0 或空),可以继续执行其他任务。
  • 同步性:应用线程仍需主动「轮询」检查 IO 操作是否就绪(而非被动等待通知)。
  • 多路复用:通过「选择器(Selector)」管理多个 IO 通道(Channel),一个线程可同时处理多个 IO 任务,大幅减少线程数量。

核心组件:

  • Channel(通道):类似流,但双向(可读可写),支持非阻塞操作(如SocketChannelFileChannel)。
  • Buffer(缓冲区):数据的容器(如ByteBuffer),IO 操作需通过缓冲区进行(先读数据到缓冲区,再从缓冲区取数据)。
  • Selector(选择器):监听多个 Channel 的事件(如 “读就绪”“连接就绪”),一个线程通过 Selector 即可管理所有 Channel,实现 “多路复用”。

1.4 AIO(Asynchronous IO,异步 IO)

AIO 是异步非阻塞 IO模型,也叫 NIO.2,JDK 1.7 引入,进一步提升 IO 效率。

核心特点:

  • 非阻塞性:IO 操作发起后,线程无需等待,可立即执行其他任务。
  • 异步性:IO 操作由操作系统完成后,通过「回调函数」主动通知应用线程(无需应用轮询)。

2.流写入与写出(基础版)

在编程中,流的写入与输出是数据处理的核心操作。流(Stream)是一种抽象的数据序列,可以是文件、网络连接或内存缓冲区。

(这里为了方便展示输出,采用字节流读取文本,实际上用字符流更好,这里可以把文本内容换成一首歌等等)

2.1流写入( 输入: 吸星大法 从源读取)

核心逻辑:

程序通过输入流,从 "数据源"(文件、网络、Socket 等)中主动读取数据,数据的流动方向是「源 → 程序」。

关键特点:

  • 数据源必须存在(否则会抛出异常,如文件不存在时FileNotFoundException)。
  • 读取过程是 "被动" 的:程序需要主动调用read()等方法才能获取数据。
  • 读完数据后必须关闭流,释放数据源的占用(如文件句柄、网络连接)。

常用输入流类及场景:

类型典型类数据源场景核心方法(吸星动作)
字节输入流FileInputStream本地二进制文件(图片、视频)read(byte[] buf):批量读取字节
Socket.getInputStream()网络 Socket(对方发送的数据)read():逐个读取字节
字符输入流BufferedReader本地文本文件、网络文本响应readLine():读取一行文本(高效)
InputStreamReader字节流转字符流(指定编码)read(char[] cbuf):批量读取字符

示例:用 "吸星大法" 读取文本文件

import java.io.*;

public class Test {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();//创建一个StringBuilder对象
        InputStream in1=null;
        try {
            in1 = new FileInputStream("./user.ser");//创建输入流
            int b;
            // 字节数组,缓冲区,提高流的读写性能,减少流的读写次数
            // 就像你家的米舀子
            byte[] buffer  = new byte[1024];
            //循环读取,吸数据
            while ((b = in1.read(buffer)) != -1) {
                sb.append(new String(buffer, 0, b)); // 只添加实际读取的字节
                System.out.println(sb.toString());
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }catch (IOException e) {
            throw new RuntimeException(e);
        }
        finally {
            if (null != in1) {
                try {
                    in1.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }//关闭输入流
        }
    }
}

输出结果:

2.2流写出( 输出,北冥神功 写到目标 )

核心逻辑:

程序通过输出流,将数据主动写入到 "目标"(文件、网络、Socket 等),数据的流动方向是「程序 → 目标」。

关键特点:

  • 目标可以不存在(如文件不存在时,输出流会自动创建,目录除外)。
  • 写入过程是 "主动" 的:程序调用write()等方法将数据推送到目标。
  • 写完后需刷新(flush) 或关闭流:缓冲流会先将数据存在内存缓冲区,刷新后才真正写入目标(关闭时会自动刷新)。

常用输出流类及场景:

类型典型类目标场景核心方法(北冥动作)
字节输出流FileOutputStream本地二进制文件(图片、视频)write(byte[] buf):批量写入字节
Socket.getOutputStream()网络 Socket(向对方发数据)write(int b):逐个写入字节
字符输出流BufferedWriter本地文本文件、网络文本发送write(String str):写入字符串
PrintWriter格式化输出(如日志、控制台)println(String str):写入一行并换

示例:用 "北冥神功" 写入文本文件

import java.io.*;

public class Test {
    public static void main(String[] args) {
        System.out.println("开始北冥神功");
        try (
                // 字符输出流(带缓冲,提高北冥效率)
                BufferedWriter writer = new BufferedWriter(new FileWriter("user.ser",true))// 创建字符输出流
        ) {
            String data = "这是要导出的数据";
            // 主动"吐"数据到目标
            writer.write(data);
            writer.newLine(); // 换行(跨平台兼容)
            writer.write("第二行数据");
            // 手动刷新(可选,关闭时会自动刷新)
            writer.flush();// 刷新缓冲区
            System.out.println("北冥神功完成,数据已导出");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意点:

BufferedWriter 会覆盖原文件内容的原因如下:
FileWriter 默认行为:
当使用 new FileWriter("user.ser") 构造函数时,默认以覆盖模式打开文件
这相当于 new FileWriter("user.ser", false),第二个参数默认为 false,改成true即可
Java IO 机制:
FileWriter 基于 FileOutputStream,默认会截断(truncate)已有文件
文件指针定位在开头位置,新写入的内容会从头开始覆盖

输出效果:

2.3输入与输出的 "组合技":数据搬运

实际开发中,输入流和输出流往往配合使用,完成 "源 → 程序 → 目标" 的完整数据搬运,比如文件复制、网络数据转发等。

示例:组合 "吸星 + 北冥" 实现文件复制

import java.io.*;

public class Main {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();//创建一个StringBuilder对象
        InputStream in1=null;
        OutputStream out1=null;
        try {
            in1 = new FileInputStream("./user.ser");//创建输入流
            File f = new File("D://user//a.txt");//创建文件对象
            if (!f.getParentFile().exists()) {
                f.getParentFile().mkdirs();
            }//创建父目录
            out1 = new FileOutputStream(f);//创建输出流
            int b;
            // 字节数组,缓冲区,提高流的读写性能,减少流的读写次数
            // 就像你家的米舀子
            byte[] buffer  = new byte[1024];
            //循环读取,吸数据
            while ((b = in1.read(buffer)) != -1) {
               out1.write(buffer);
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }catch (IOException e) {
            throw new RuntimeException(e);
        }
        finally {
            if (null != in1) {
                try {
                    in1.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }//关闭输入流
        }
    }
}

运行效果:

3.java.io常用的类有哪些

(微信公众号看到的比较齐全的图)

1.实际操作常用的用法

他们的类看着非常多而且繁琐,建议组合使用。

例如:

FileInputStream
用途: 用于从文件系统中读取文件数据
特点: 直接与文件关联,每次读取都会触发系统调用
性能: 频繁的小量读取操作效率较低


 BufferedInputStream
用途: 为其他输入流添加缓冲功能
特点:
内部维护缓冲区,减少系统调用次数
显著提升频繁读取操作的性能
支持 mark() 和 reset() 方法
用法: 通常包装 FileInputStream 或其他基础输入流

// 推荐的组合使用方式
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("file.txt"));

这样的方式结合了 FileInputStream 的文件访问能力和 BufferedInputStream 的缓冲优化。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐