一、IO 流完整分类体系

1. 两大核心分支:字节流 & 字符流

1.1 字节流(InputStream/OutputStream)
  • 处理单位:单个字节byte,适配所有文件类型(图片、视频、压缩包、二进制文件)
  • 顶层父类:InputStream(输入读)、OutputStream(输出写)
  • 常用实现类:
    • 文件读写:FileInputStream / FileOutputStream
    • 缓冲加速:BufferedInputStream / BufferedOutputStream
    • 字节数组:ByteArrayInputStream
1.2 字符流(Reader/Writer)
  • 处理单位:字符char,仅适配文本文件(txt、java、md),内置编码转换
  • 顶层父类:Reader / Writer
  • 常用实现类:
    • 文件文本:FileReader / FileWriter
    • 缓冲提速:BufferedReader / BufferedWriter
    • 按行读取:BufferedReader.readLine()

1.2 核心区别对比表

表格

维度 字节流 字符流
操作单元 字节 (8bit) 字符 (16bit)
适用文件 二进制所有文件 纯文本文件
编码处理 无,需手动转码 自动处理字符编码
缓冲特性 无内置缓冲,推荐手动套 Buffered 自带字符缓冲区

1.3 节点流 vs 处理流

  1. 节点流:直接对接文件 / 数组等数据源,如FileInputStream
  2. 处理流(装饰器模式):包裹节点流增强功能,如缓冲流、对象流、转换流

二、文件复制 4 种实现方式性能对比

方式 1:基础字节流单字节读写(性能最差)

缺点:频繁磁盘 IO,循环单字节读取,大文件直接卡顿

java

运行

// 单字节拷贝,不推荐
public static void copyByByte(String src, String dest) throws IOException {
    try (FileInputStream fis = new FileInputStream(src);
         FileOutputStream fos = new FileOutputStream(dest)) {
        int data;
        while ((data = fis.read()) != -1) {
            fos.write(data);
        }
    }
}

方式 2:字节流自定义数组缓冲区(中等性能)

自定义 byte 数组批量读取,减少 IO 交互,日常小文件够用

java

运行

public static void copyByByteArray(String src, String dest) throws IOException {
    try (FileInputStream fis = new FileInputStream(src);
         FileOutputStream fos = new FileOutputStream(dest)) {
        // 自定义8KB缓冲区
        byte[] buffer = new byte[8192];
        int len;
        while ((len = fis.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }
    }
}

方式 3:缓冲流 Buffered 包装(传统 IO 最优)

底层内置 8192 字节缓冲,减少系统调用,中小型文件首选

java

运行

public static void copyByBuffered(String src, String dest) throws IOException {
    try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
         BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest))) {
        byte[] buf = new byte[1024];
        int len;
        while ((len = bis.read(buf)) != -1) {
            bos.write(buf, 0, len);
        }
        bos.flush(); // 强制刷新缓冲区残留数据
    }
}

方式 4:NIO 通道 transferTo 零拷贝(超大文件最优)

操作系统层面零拷贝,不经过应用内存,减少内核态 / 用户态切换

java

运行

public static void copyByNIOTransfer(String src, String dest) throws IOException {
    try (FileChannel inChannel = new FileInputStream(src).getChannel();
         FileChannel outChannel = new FileOutputStream(dest).getChannel()) {
        // 零拷贝传输
        inChannel.transferTo(0, inChannel.size(), outChannel);
    }
}

四种方式性能结论

  1. 单字节读写:最慢,禁止用于 100MB 以上文件
  2. 自定义数组缓冲:适合 100MB 内小文件
  3. Buffered 缓冲流:传统 IO 通用最优方案
  4. NIO transferTo:GB 级大文件、视频 / 镜像首选

三、NIO 核心三大组件详解

3.1 Channel 通道

  • 特点:双向读写(传统 IO 流单向),支持异步、零拷贝
  • 常用实现:FileChannel(文件)、SocketChannel(网络)
  • 核心方法:read(Buffer)write(Buffer)transferTo()

3.2 Buffer 缓冲区(数据中转容器)

四大核心标记:

  1. capacity:缓冲区总容量,初始化固定不变
  2. limit:读写边界,写模式 = capacity,读模式 = 数据末尾
  3. position:当前读写下标,随读写自动移动
  4. mark:临时标记,配合 reset 回退 position

java

运行

public void bufferTest() {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    // 写入模式
    buffer.put("Java NIO".getBytes());
    buffer.flip(); // 切换读模式,limit=当前position,position归零

    // 循环读取
    while (buffer.hasRemaining()) {
        System.out.print((char) buffer.get());
    }
    buffer.clear(); // 清空,回到写模式
}

3.3 Path & Files 工具类(NIO.2 文件工具)

JDK7 新增,替代老旧 File 类,简化文件增删改查

java

运行

// 1. Path路径构建
Path srcPath = Paths.get("D:/test/file.txt");
Path destPath = Paths.get("D:/copy/file.txt");

// 2. Files工具类常用操作
Files.exists(srcPath); // 判断文件是否存在
Files.createDirectories(Paths.get("D:/nio")); // 创建多级目录
Files.copy(srcPath, destPath, StandardCopyOption.REPLACE_EXISTING); // 文件复制
List<String> lines = Files.readAllLines(srcPath, StandardCharsets.UTF_8); // 读取全部文本

优势:代码简洁,内置编码、覆盖、权限等参数,无需手动封装流。

四、大文件高效读取方案(避免 OOM 内存溢出)

方案 1:BufferedReader 按行读取文本文件

千万行日志文件,不一次性加载全量内容到内存

java

运行

try (BufferedReader br = Files.newBufferedReader(Paths.get("log.txt"), StandardCharsets.UTF_8)) {
    String line;
    // 逐行加载,内存只存单行数据
    while ((line = br.readLine()) != null) {
        handleLine(line); // 单行业务处理
    }
}

方案 2:NIO 分片分段读取超大二进制文件

按固定分片读取,控制单次加载内存上限,适配 GB 级视频、压缩包

java

运行

// 每次读取16MB分片
int chunkSize = 16 * 1024 * 1024;
try (FileChannel channel = FileChannel.open(Paths.get("big.rar"), StandardOpenOption.READ)) {
    long totalSize = channel.size();
    long position = 0;
    ByteBuffer buffer = ByteBuffer.allocate(chunkSize);
    while (position < totalSize) {
        buffer.clear();
        int readLen = channel.read(buffer, position);
        if (readLen == -1) break;
        buffer.flip();
        // 处理分片数据
        processChunk(buffer);
        position += readLen;
    }
}

方案 3:Files.lines 流式处理(JDK8+ Stream)

流式惰性加载,配合流式过滤、统计,内存友好

java

运行

// 筛选包含异常关键字的日志行
try (Stream<String> stream = Files.lines(Paths.get("app.log"))) {
    stream.filter(line -> line.contains("Exception"))
          .forEach(System.out::println);
}

五、资源关闭规范:try-with-resources 最佳实践

1. 传统 try-catch-finally 写法(冗余、易出错)

缺陷:双层 try-catch,忘记关闭流、关闭时报空指针

java

运行

// 老旧不推荐写法
FileInputStream fis = null;
try {
    fis = new FileInputStream("test.txt");
    // 读写逻辑
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2. try-with-resources 标准写法(JDK7 推荐)

实现AutoCloseable接口的流 / 通道会自动关闭,无需手动调用 close () 核心规则:资源定义在 try () 括号内,无论正常结束还是异常抛出,自动释放

java

运行

// 多个资源用分号分隔,自动关闭顺序与声明相反
try (BufferedReader br = Files.newBufferedReader(Paths.get("test.txt"));
     BufferedWriter bw = Files.newBufferedWriter(Paths.get("output.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        bw.write(line);
        bw.newLine();
    }
} catch (IOException e) {
    // 统一异常处理
    log.error("文件读写失败", e);
}

3. 常见踩坑提醒

  1. 缓冲流必须在关闭前 flush (),try-with-resources 关闭时会自动执行 flush
  2. 不要把资源定义在 try 外部,否则无法自动释放
  3. 数据库连接、Socket、IO 流全部适用该语法

六、全文总结

  1. 区分场景选流:二进制文件用字节流,纯文本优先字符流;
  2. 传统 IO 提升性能必须搭配 Buffered 缓冲流,大文件直接使用 NIO transferTo 零拷贝;
  3. 放弃老旧 File 类,新项目统一使用 NIO.2 Path+Files 工具类;
  4. 超大文件禁止一次性读取全量数据,采用按行 / 分片读取防止 OOM;
  5. 所有 IO 资源强制使用try-with-resources自动关闭,杜绝文件句柄泄漏。

更多推荐