Java缓冲流与转换流全面解析

 更新时间:2026年06月08日 09:58:54   作者:许彰午  
本文深入讲解Java IO中的的缓冲流与转换流,包括BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter、InputStreamReader和OutputStreamWriter的设计原理、使用方法及性能对比,强调装饰器模式在流组合中的应用

前言

上一篇我们学习了Java IO流的体系框架和基础用法。在实际开发中,直接使用FileInputStreamFileReader进行逐字节或逐字符读写效率较低,因为每次read操作都会触发一次磁盘IO。Java提供了缓冲流转换流来解决性能和编码问题。缓冲流通过内置缓存区减少实际IO次数,转换流则在字节流和字符流之间架起桥梁。

理解这两类流是掌握Java IO"装饰器模式"的关键一步。如果把基础流(FileInputStream等)看作"水管",缓冲流就是"水塔"——先把水存储起来,再批量传输;转换流则是"翻译官"——帮你在字节和字符之间进行编码转换。两者结合使用,构成了Java IO编程中最常见的实践模式。本文将深入讲解这两类流的设计原理、使用方法和性能对比。

一、缓冲流的原理

普通IO流每次读/写操作都需要与底层设备交互,产生大量系统调用,性能较低。缓冲流内部维护了一个缓冲区数组。为了理解缓冲流的重要性,可以做一个类比:如果没有缓冲,就相当于每次去银行只取1块钱,一天要去100次;有了缓冲,就是一次取100块钱,只去1次。系统调用的开销是IO性能的主要瓶颈,而缓冲流正是通过"批处理"来减少这个开销。

  • 读操作:一次性从磁盘读取大量数据到缓冲区,后续read直接从缓冲区获取,减少磁盘IO
  • 写操作:数据先写入缓冲区,当缓冲区满或调用flush()时才一次性写入磁盘
// 缓冲流工作原理示意(伪代码)
class BufferedInputStream extends FilterInputStream {
    byte[] buf = new byte[8192];   // 默认8KB缓冲区
    int count;                      // 缓冲区有效字节数
    int pos;                        // 当前读取位置
    public int read() {
        if (pos >= count) {
            fill();                  // 缓冲区空了,从底层流填充
        }
        return buf[pos++];
    }
}

缓冲区大小默认是8192字节(8KB),可以通过构造方法自定义。

二、字节缓冲流

2.1 BufferedInputStream

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class BufferedInputStreamDemo {
    public static void main(String[] args) {
        long start, end;
        // 不使用缓冲流读取
        start = System.currentTimeMillis();
        try (FileInputStream fis = new FileInputStream("D:/test/large_file.dat")) {
            int data;
            while ((data = fis.read()) != -1) {
                // 逐字节读取(极慢)
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        end = System.currentTimeMillis();
        System.out.println("无缓冲耗时:" + (end - start) + "ms");
        // 使用缓冲流读取
        start = System.currentTimeMillis();
        try (BufferedInputStream bis = new BufferedInputStream(
                new FileInputStream("D:/test/large_file.dat"))) {
            int data;
            while ((data = bis.read()) != -1) {
                // 同样逐字节读取,但背后有缓冲区(快很多)
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        end = System.currentTimeMillis();
        System.out.println("有缓冲耗时:" + (end - start) + "ms");
    }
}

装饰器模式体现在此:BufferedInputStream包装了FileInputStream,在不改变原有接口的前提下增强了功能。

2.2 BufferedOutputStream

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedOutputStreamDemo {
    public static void main(String[] args) {
        try (BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream("D:/test/buffer_output.txt"))) {
            for (int i = 0; i < 1000; i++) {
                bos.write(("第" + (i + 1) + "行数据\r\n").getBytes());
                // 数据先写入缓冲区,减少磁盘IO次数
            }
            // flush()将缓冲区数据强制写入磁盘
            bos.flush();
            System.out.println("批量写入完成!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意:close()方法会自动调用flush(),所以使用try-with-resources时通常不需要手动调用flush()。

三、字符缓冲流

3.1 BufferedReader

BufferedReader是字符缓冲输入流,提供了逐行读取的便捷方法:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReaderDemo {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(
                new FileReader("D:/test/poem.txt"))) {
            String line;
            int lineNum = 1;
            // readLine() 逐行读取,读到末尾返回null
            while ((line = br.readLine()) != null) {
                System.out.println((lineNum++) + ": " + line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

readLine()方法是BufferedReader独有的,其父类Reader中没有此方法,这是缓冲流提供的重要增强。

3.2 BufferedWriter

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedWriterDemo {
    public static void main(String[] args) {
        try (BufferedWriter bw = new BufferedWriter(
                new FileWriter("D:/test/student_list.txt"))) {
            bw.write("学号\t姓名\t成绩");
            bw.newLine();   // 写入系统相关的换行符(跨平台兼容)
            bw.write("001\t张三\t95");
            bw.newLine();
            bw.write("002\t李四\t88");
            bw.newLine();
            bw.write("003\t王五\t92");
            System.out.println("成绩单保存成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

bw.newLine()是跨平台的换行方法,会根据操作系统自动选择\r\n(Windows)或\n(Linux/Mac)。

四、转换流:InputStreamReader

InputStreamReader是字节流到字符流的桥梁,它将字节输入流按照指定的字符编码转换为字符输入流:

import java.io.*;
public class InputStreamReaderDemo {
    public static void main(String[] args) {
        // 读取GBK编码的文件(Windows中文系统默认编码)
        try (InputStreamReader isr = new InputStreamReader(
                new FileInputStream("D:/test/gbk_file.txt"), "GBK");
             BufferedReader br = new BufferedReader(isr)) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 对比:FileReader使用系统默认编码,可能产生乱码
        try (BufferedReader br = new BufferedReader(
                new FileReader("D:/test/gbk_file.txt"))) {
            // 如果文件是GBK编码,而系统默认是UTF-8,此处就会乱码
            String line = br.readLine();
            System.out.println("FileReader读取:" + line);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

关键理解

  • FileReader = new InputStreamReader(new FileInputStream(...))(使用系统默认编码)
  • 当需要指定非默认编码时,必须使用InputStreamReader

五、转换流:OutputStreamWriter

OutputStreamWriter是字符流到字节流的桥梁:

import java.io.*;
public class OutputStreamWriterDemo {
    public static void main(String[] args) {
        String content = "这是一段中文文本,包含特殊字符 © ® ™";
        // 写入为UTF-8编码文件
        try (OutputStreamWriter osw = new OutputStreamWriter(
                new FileOutputStream("D:/test/utf8_output.txt"), "UTF-8");
             BufferedWriter bw = new BufferedWriter(osw)) {
            bw.write(content);
            System.out.println("UTF-8文件写入成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 写入为GBK编码文件(相同内容,不同编码)
        try (OutputStreamWriter osw = new OutputStreamWriter(
                new FileOutputStream("D:/test/gbk_output.txt"), "GBK");
             BufferedWriter bw = new BufferedWriter(osw)) {
            bw.write(content);
            System.out.println("GBK文件写入成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

六、流的装饰组合

Java IO流的核心设计模式是装饰器模式,可以通过层层嵌套组合功能:

import java.io.*;
public class StreamDecoratorDemo {
    public static void main(String[] args) throws IOException {
        // 经典三层装饰组合
        // FileInputStream(底层) → InputStreamReader(解码) → BufferedReader(缓冲+按行读)
        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(
                        new FileInputStream("D:/test/data.txt"), "UTF-8"))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        }
        // 写入组合
        // FileOutputStream(底层) → OutputStreamWriter(编码) → BufferedWriter(缓冲+换行)
        try (BufferedWriter bw = new BufferedWriter(
                new OutputStreamWriter(
                        new FileOutputStream("D:/test/result.txt"), "UTF-8"))) {
            bw.write("装饰器模式组合示例");
            bw.newLine();
            bw.write("灵活高效");
        }
    }
}

装饰器链条使得每种流只负责单一职责:

  • FileInputStream:负责与文件交互
  • InputStreamReader:负责字节到字符的转换和编码
  • BufferedReader:负责缓冲和提供便利方法

七、性能对比实验

import java.io.*;
public class PerformanceTest {
    static final String SRC = "D:/test/large.dat";
    static final String DEST = "D:/test/copy.dat";
    public static void main(String[] args) throws IOException {
        // 1. 基础字节流(最慢)
        testCopy("基础字节流", new FileInputStream(SRC), new FileOutputStream(DEST));
        // 2. 缓冲字节流
        testCopy("缓冲字节流",
                new BufferedInputStream(new FileInputStream(SRC)),
                new BufferedOutputStream(new FileOutputStream(DEST)));
        // 3. 自定义缓冲区字节流
        testCopy("自定义缓冲区(4KB)",
                new FileInputStream(SRC), new FileOutputStream(DEST), 4096);
    }
    static void testCopy(String name, InputStream in, OutputStream out)
            throws IOException {
        long start = System.currentTimeMillis();
        try (in; out) {
            byte[] buf = new byte[1024];
            while (in.read(buf) != -1) {
                out.write(buf);
            }
        }
        long end = System.currentTimeMillis();
        System.out.println(name + " 耗时:" + (end - start) + "ms");
    }
    static void testCopy(String name, InputStream in, OutputStream out, int bufSize)
            throws IOException {
        long start = System.currentTimeMillis();
        try (in; out) {
            byte[] buf = new byte[bufSize];
            int len;
            while ((len = in.read(buf)) != -1) {
                out.write(buf, 0, len);
            }
        }
        long end = System.currentTimeMillis();
        System.out.println(name + " 耗时:" + (end - start) + "ms");
    }
}

总结

本文深入讲解了Java IO中的缓冲流与转换流:

  • 缓冲流(BufferedInputStream / BufferedOutputStream / BufferedReader / BufferedWriter):通过内置缓冲区减少磁盘IO次数,显著提升读写性能。BufferedReader的readLine()和BufferedWriter的newLine()是常用的便捷方法
  • 转换流(InputStreamReader / OutputStreamWriter):在字节流和字符流之间建立桥梁,支持指定字符编码,解决跨编码场景下的乱码问题
  • 装饰器模式:Java IO通过层层包装组合不同流的功能,实现了高度的灵活性和可扩展性

在实际项目中,推荐始终使用缓冲流包裹基础流,并根据需要添加转换流来指定编码。

✅ 亮点总结

  • 缓冲流内部默认8KB缓冲区的工作机制:一次磁盘IO填充缓冲区,后续read()直接从内存读取,大幅减少系统调用
  • BufferedReader/BufferedWriter提供的readLine()和newLine()便捷方法,newLine()自动适配操作系统换行符
  • InputStreamReader/OutputStreamWriter作为字节-字符桥梁,核心价值在于允许显式指定字符编码(GBK/UTF-8)
  • FileReader本质是new InputStreamReader(new FileInputStream(...), 默认编码),指定编码时需显式使用转换流
  • 装饰器模式三层经典组合:底层FileStream(文件交互)→转换流(编码处理)→缓冲流(性能优化+便利方法)

适用场景

  • 大文件日志的逐行分析与数据统计,利用BufferedReader逐行读取避免一次性加载
  • 跨编码系统的数据迁移,如GBK编码的遗留系统数据转换为UTF-8存入新系统
  • 高效文件读写工具类的封装,统一对外提供按行读取、批量写入等便捷接口

扩展方向

  • 深入学习Java NIO的Channel与Buffer(非阻塞IO),理解BIO到NIO的架构升级
  • 研究Java 7 NIO.2的Files工具类(Path、Files.copy/walk等现代化文件操作API)

到此这篇关于Java缓冲流与转换流全面解析的文章就介绍到这了,更多相关Java缓冲流与转换流内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • mybatisplus驼峰命名映射的问题解决

    mybatisplus驼峰命名映射的问题解决

    本文主要介绍了mybatisplus驼峰命名映射的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • SpringMVC处理器映射器HandlerMapping详解

    SpringMVC处理器映射器HandlerMapping详解

    这篇文章主要介绍了SpringMVC处理器映射器HandlerMapping详解,在SpringMVC中会有很多请求,每个请求都需要一个HandlerAdapter处理,具体接收到一个请求之后使用哪个HandlerAdapter进行处理呢,他们的过程是什么,需要的朋友可以参考下
    2023-09-09
  • Springboot+JPA打印SQL日志过程

    Springboot+JPA打印SQL日志过程

    文章说明在Spring Boot项目中配置Lombok插件、application.yml和logback.xml,以实现从前端接收活动名称参数,并输出SQL语句及查询参数用于调试
    2025-08-08
  • java String.join()的使用小结

    java String.join()的使用小结

    String.join()是Java 8引入的一个实用方法,用于将多个字符串按照指定分隔符连接成一个字符串,本文主要介绍了java String.join()的使用小结,感兴趣的可以了解一下
    2025-03-03
  • Java拆装箱深度剖析

    Java拆装箱深度剖析

    这篇文章主要为大家深度剖析了Java拆箱装箱的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-12-12
  • Java并发编程中的synchronized关键字详细解读

    Java并发编程中的synchronized关键字详细解读

    这篇文章主要介绍了Java并发编程中的synchronized关键字详细解读,在Java早期版本中,synchronized 属于 重量级锁,效率低下,这是因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,Java 的线程是映射到操作系统的原生线程之上的,需要的朋友可以参考下
    2023-12-12
  • 详解Java如何实现防止恶意注册

    详解Java如何实现防止恶意注册

    恶意注册通常是指使用自动化脚本或者机器人在短时间内进行大量的注册行为,这种行为会对系统造成压力,甚至会导致系统瘫痪。所以本文为大家总结了一些防止恶意注册的方法,需要的可以参考一下
    2023-04-04
  • django 递归查询评论的示例详解

    django 递归查询评论的示例详解

    文章介绍了将表数据转换为树状结构以及如何处理扁平化数据的方法,探讨了在数据管理中的应用和实现技术,感兴趣的朋友一起看看吧
    2025-01-01
  • JAVA开发环境搭建教程

    JAVA开发环境搭建教程

    这篇文章主要为大家详细介绍了JAVA开发环境搭建教程,配置JAVA开发环境,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • Java 网络编程socket编程等详解

    Java 网络编程socket编程等详解

    本篇文章主要介绍了java网络编程中的类的方法以及实例,需要的朋友可以参考下
    2017-04-04

最新评论