Skip to content

缓冲流与包装流

概述

缓冲流在底层流(如 字节流字符流)之上增加内存缓冲区,减少与磁盘或网络的直接读写次数,从而显著提升 IO 性能。包装流(也称装饰器流)是指「接收另一个流作为参数、在其上增加某种能力」的流;缓冲流就是典型的包装流——它们包装 InputStream/OutputStreamReader/Writer,为其增加缓冲与便捷方法(如 BufferedReader.readLine())。

理解缓冲流与包装流,是写出高效、可维护 IO 代码的基础,也是学习 NIO 中 Buffer 概念的前置。


缓冲流与包装流体系概览

类型常用类包装对象说明
字节缓冲流BufferedInputStreamInputStream为字节输入流增加缓冲,减少 read() 系统调用
BufferedOutputStreamOutputStream为字节输出流增加缓冲,批量写入底层流
字符缓冲流BufferedReaderReader为字符输入流增加缓冲,并提供 readLine() 按行读取
BufferedWriterWriter为字符输出流增加缓冲,并提供 newLine() 换行
  • 包装关系:缓冲流通过构造方法接收「底层流」,例如 new BufferedInputStream(new FileInputStream("a.txt"))。关闭缓冲流时,会关闭其包装的底层流,因此只需关闭最外层流
  • 默认缓冲区大小:通常为 8KB(8192 字节/字符),也可在构造时指定,如 new BufferedInputStream(in, 16384)

提示

多数文件读写场景下,应优先使用缓冲流包装 FileInputStream/FileOutputStreamFileReader/FileWriter,可明显减少 IO 次数,尤其在循环中逐字节/逐字符读写时。


基本用法

BufferedInputStream / BufferedOutputStream

  • 构造BufferedInputStream(InputStream in)BufferedInputStream(InputStream in, int size)BufferedOutputStream(OutputStream out)BufferedOutputStream(OutputStream out, int size)
  • 读写:与底层 InputStream/OutputStreamread(byte[])write(byte[]) 用法一致;数据先经过内部缓冲区,再与底层流交互。
  • flushBufferedOutputStream.flush() 将缓冲区内容写入底层流;BufferedInputStreamflush
  • close:关闭时会关闭包装的底层流,并释放缓冲区。

BufferedReader / BufferedWriter

  • 构造BufferedReader(Reader in)BufferedReader(Reader in, int size)BufferedWriter(Writer out)BufferedWriter(Writer out, int size)
  • 读写:与底层 Reader/Writerread(char[])write(String) 等用法一致;另提供:
    • String readLine():读取一行(不包含换行符),末尾返回 null
    • void newLine():写出与系统无关的换行符。
  • flushBufferedWriter.flush() 将缓冲区内容写入底层流;写入后若需立即落盘,应调用 flush()
  • close:关闭时会先 flush(若有未写出数据),再关闭包装的底层流。

说明

缓冲流的 readwriteclose 会抛出受检异常 IOException,必须用 try-catch 捕获或在方法上 throws IOException。推荐用 try-with-resources 管理流,只需关闭最外层缓冲流即可。详见 异常处理


使用示例

示例 1:用 BufferedInputStream 包装 FileInputStream 读取文件

java
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class BufferedReadFile {
    public static void main(String[] args) {
        // 缓冲流包装文件流;try-with-resources 只关闭 BufferedInputStream,其会关闭内部 FileInputStream
        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream("data/hello.txt"))) {
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) != -1) {
                // 实际读到的字节数为 len,可能小于 buf.length
                System.out.print(new String(buf, 0, len));
            }
        } catch (IOException e) {
            System.err.println("读取失败: " + e.getMessage());
        }
    }
}

示例 2:用 BufferedWriter 包装 FileWriter 写入并 flush

java
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedWriteFile {
    public static void main(String[] args) {
        try (BufferedWriter out = new BufferedWriter(new FileWriter("data/output.txt"))) {
            out.write("第一行");
            out.newLine();  // 与系统无关的换行
            out.write("第二行");
            out.flush();    // 确保写入缓冲区的内容立即写到文件(try-with-resources 关闭前也会 flush)
        } catch (IOException e) {
            System.err.println("写入失败: " + e.getMessage());
        }
    }
}

示例 3:BufferedReader 按行读取(readLine)

java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class ReadByLine {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("data/hello.txt"))) {
            String line;
            int lineNum = 0;
            while ((line = reader.readLine()) != null) {
                lineNum++;
                System.out.println(lineNum + ": " + line);
            }
        } catch (IOException e) {
            System.err.println("读取失败: " + e.getMessage());
        }
    }
}

提示

BufferedReader.readLine() 是处理文本文件按行读取最常用的方式;返回的字符串不包含换行符。若需指定编码,可包装 InputStreamReadernew BufferedReader(new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8))

示例 4:多层包装与关闭顺序

java
import java.io.*;
import java.nio.charset.StandardCharsets;

public class LayeredStreams {
    public static void main(String[] args) {
        // 从内到外:FileInputStream → InputStreamReader(字节转字符,指定 UTF-8)→ BufferedReader
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(new FileInputStream("data/hello.txt"), StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("读取失败: " + e.getMessage());
        }
        // 只需关闭最外层的 BufferedReader;其 close() 会依次关闭 InputStreamReader、FileInputStream
    }
}

包装流(装饰器)概念

Java IO 中大量使用装饰器模式:用一个流「包装」另一个流,在不修改原有类的前提下增加缓冲、编码转换、行读写等能力。

概念说明
底层流直接与数据源/目标打交道,如 FileInputStreamFileWriter
包装流接收一个已有流,在其上增加行为,如 BufferedInputStreamInputStreamReader
关闭顺序只需关闭最外层包装流;其 close() 会委托给内层流,最终关闭底层流
flushBufferedOutputStreamBufferedWriter 等有缓冲的输出流,flush() 将缓冲区内容推到下层;关闭时一般会先 flush

注意

不要重复关闭已被包装的底层流——例如不要把 FileInputStream 和包装它的 BufferedInputStream 都放进 try-with-resources 并分别关闭,只关闭最外层的 BufferedInputStream 即可,否则可能导致「流已关闭」的异常或重复关闭。


注意事项

  1. 何时使用缓冲流:对文件、网络等「每次 read/write 成本较高」的流,建议用缓冲流包装;对 ByteArrayInputStream 等已在内存中的流,包装缓冲流收益较小。
  2. 输出流记得 flush:在需要立即将数据写入磁盘或网络时(如日志、协议报文),在 close() 前可显式调用 flush();使用 try-with-resources 时,close() 会先 flush 再关闭。
  3. readLine 与编码BufferedReaderreadLine() 依赖底层 Reader 的编码;若需指定 UTF-8 等,用 InputStreamReader 包装 FileInputStream 并传入 Charset,再包装为 BufferedReader
  4. 缓冲区大小:默认 8KB 对多数场景已足够;大文件或高吞吐时可适当增大,如 new BufferedInputStream(in, 65536)

注意

不要在多线程中共享同一个流实例(包括缓冲流);流通常不是线程安全的,多线程读写应使用各自独立的流或使用线程安全的 API。


相关链接

基于 VitePress 构建