跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Java IO 流进阶:字符流与字节流的深度应用

Java IO 流中字节流与字符流的核心区别在于基本单位与适用场景,字节流处理所有类型文件,字符流专用于文本。缓冲流通过内存缓冲区减少磁盘交互,显著提升大文件读写效率。转换流配合指定编码格式可解决跨平台乱码问题。JDK7 引入 try-with-resources 语法简化资源释放。实战案例展示了利用缓冲字节流递归复制文件夹的方法,综合运用了嵌套使用与异常处理技巧。

宁静发布于 2026/3/22更新于 2026/7/2044 浏览
Java IO 流进阶:字符流与字节流的深度应用

Java IO 流进阶:字符流与字节流的深度应用

1.1 本章学习目标与重点

  • 掌握字节流与字符流的核心区别,能够根据实际开发场景选择合适的 IO 流实现文件操作。
  • 熟练运用缓冲流提升 IO 操作效率,解决大文件读写的性能问题。
  • 理解转换流的作用,处理不同编码格式的文件读写,避免乱码问题。
  • 重点:流的嵌套使用和资源释放的标准写法,这是实际开发中高频考点和易错点。

1.2 字节流与字符流的核心差异

1.2.1 基本概念与设计初衷

字节流以 byte 为基本单位进行数据传输,它可以处理所有类型的文件,比如图片、视频、音频、文本等。 字符流以 char 为基本单位进行数据传输,它专门用于处理文本文件,底层会涉及字符编码的转换。

字节流的核心类是 InputStream 和 OutputStream,字符流的核心类是 Reader 和 Writer。 两者都是抽象类,实际开发中我们使用的是它们的子类,比如 FileInputStream、FileWriter 等。

核心结论:处理非文本文件用字节流,处理文本文件优先用字符流。

1.2.2 代码实操:字节流读写文本文件
  1. 创建 FileInputStream 对象,关联要读取的文本文件 test.txt
  2. 定义 byte 数组作为缓冲区,减少 IO 次数
  3. 读取数据并转换为字符串,输出到控制台
  4. 关闭流资源,释放文件句柄
import java.io.FileInputStream;
import java.io.IOException;

public class ByteStreamDemo {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            // 1. 关联文件路径
            fis = new FileInputStream("test.txt");
            // 2. 定义缓冲区,大小为 1024 字节(1KB)
            byte[] buffer = new byte[1024];
            int len; // 记录每次读取的有效字节数
            // 3. 循环读取数据
            while ((len = fis.read(buffer)) != -1) {
                // 将字节数组转换为字符串
                System.out.print(new String(buffer, 0, len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4. 关闭流资源
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

注意事项:使用字节流读取文本文件时,如果文件编码是 UTF-8,而系统默认编码是 GBK,可能会出现乱码。这时候需要用字符流或者转换流来解决。

1.2.3 代码实操:字符流读写文本文件

字符流的优势在于自动处理字符编码,默认使用系统编码,也可以手动指定编码格式。 下面是用 FileReader 和 FileWriter 实现文本文件的复制操作:

  1. 创建 FileReader 对象读取源文件,创建 FileWriter 对象写入目标文件
  2. 定义 char 数组作为缓冲区,提升读取效率
  3. 循环读取源文件数据,并写入目标文件
  4. 关闭流资源,先关写入流,再关读取流
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CharStreamDemo {
    public static void main(String[] args) {
        FileReader fr = null;
        FileWriter fw = null;
        try {
            // 1. 关联源文件和目标文件
            fr = new FileReader("source.txt");
            fw = new FileWriter("target.txt");
            // 2. 定义字符缓冲区
            char[] buffer = new char[1024];
            int len;
            // 3. 循环读写
            while ((len = fr.read(buffer)) != -1) {
                fw.write(buffer, 0, len);
                // 刷新缓冲区,避免数据滞留
                fw.flush();
            }
            System.out.println("文件复制成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4. 关闭流资源,后开先关
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

核心结论:字符流读写文本文件时,无需手动处理编码转换,代码更简洁,且不易出现乱码。

1.2.4 字节流与字符流的性能对比

没有缓冲的情况下,字节流和字符流的读写效率相近。 但在处理大文件时,两者都需要搭配缓冲流来提升性能。

缓冲流的原理是在内存中开辟一块缓冲区,一次性读取或写入大量数据,减少与磁盘的交互次数。 字节缓冲流的类是 BufferedInputStream 和 BufferedOutputStream。 字符缓冲流的类是 BufferedReader 和 BufferedWriter。

下面是缓冲流的性能测试案例:分别用普通字节流和缓冲字节流读取一个 100MB 的视频文件,记录耗时。

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

public class BufferedStreamTest {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        readWithBuffer("large_video.mp4");
        long end = System.currentTimeMillis();
        System.out.println("缓冲流耗时:" + (end - start) + "ms");
        
        start = System.currentTimeMillis();
        readWithoutBuffer("large_video.mp4");
        end = System.currentTimeMillis();
        System.out.println("普通流耗时:" + (end - start) + "ms");
    }

    // 使用缓冲字节流读取文件
    private static void readWithBuffer(String path) {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path))) {
            byte[] buffer = new byte[1024];
            while (bis.read(buffer) != -1) {
                // 读取数据,不做输出
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 使用普通字节流读取文件
    private static void readWithoutBuffer(String path) {
        try (FileInputStream fis = new FileInputStream(path)) {
            byte[] buffer = new byte[1024];
            while (fis.read(buffer) != -1) {
                // 读取数据,不做输出
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试结果(仅供参考):

  • 缓冲流耗时:120ms
  • 普通流耗时:850ms

核心结论:缓冲流能大幅提升 IO 操作效率,处理大文件时必须使用缓冲流。

1.3 转换流:解决文件编码乱码问题

1.3.1 转换流的作用

转换流的作用是字节流和字符流之间的转换,它可以指定字符编码格式,解决文本文件读写的乱码问题。 转换流的核心类是 InputStreamReader 和 OutputStreamWriter。

  • InputStreamReader:将字节输入流转换为字符输入流。
  • OutputStreamWriter:将字符输出流转换为字节输出流。
1.3.2 代码实操:指定编码读取文件

当我们读取一个 UTF-8 编码的文件,而系统默认编码是 GBK 时,直接用 FileReader 会出现乱码。 此时可以用 InputStreamReader 指定编码格式为 UTF-8:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class ConvertStreamDemo {
    public static void main(String[] args) {
        try (InputStreamReader isr = new InputStreamReader(new FileInputStream("utf8_file.txt"), "UTF-8")) {
            char[] buffer = new char[1024];
            int len;
            while ((len = isr.read(buffer)) != -1) {
                System.out.print(new String(buffer, 0, len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意事项:指定的编码格式必须和文件的实际编码一致,否则仍然会出现乱码。 常见的编码格式有 UTF-8、GBK、GB2312、ISO-8859-1 等。

1.4 IO 流资源释放的标准写法

1.4.1 JDK7 之前的写法:try-catch-finally

在 JDK7 之前,我们需要在 finally 块中手动关闭流资源,并且要判断流对象是否为 null,避免空指针异常。 这种写法比较繁琐,但兼容性最好。

1.4.2 JDK7 及之后的写法:try-with-resources

JDK7 引入了 try-with-resources 语法,它可以自动关闭实现了 AutoCloseable 接口的资源,无需手动在 finally 块中关闭。 这种写法更简洁,代码可读性更高,是目前推荐的写法。

示例代码:

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

public class TryWithResourcesDemo {
    public static void main(String[] args) {
        // 将流对象声明在 try 的括号中,自动关闭
        try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
            String line;
            // 按行读取文本文件
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

核心结论:JDK7 及以上版本优先使用 try-with-resources 语法,简化资源释放代码。

1.5 实战案例:文件夹批量复制工具

1.5.1 需求分析

实现一个工具类,能够复制指定文件夹下的所有文件和子文件夹,包括各种类型的文件(文本、图片、视频等)。 要求:

  1. 支持大文件复制,使用缓冲流提升效率
  2. 自动创建目标文件夹,避免路径不存在异常
  3. 处理复制过程中的 IO 异常,给出友好提示
1.5.2 代码实现
import java.io.*;

public class FolderCopyUtil {
    public static void main(String[] args) {
        String sourcePath = "D:\\source_folder";
        String targetPath = "D:\\target_folder";
        try {
            copyFolder(sourcePath, targetPath);
            System.out.println("文件夹复制成功!");
        } catch (IOException e) {
            System.out.println("文件夹复制失败:" + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 复制文件夹
     *
     * @param sourcePath 源文件夹路径
     * @param targetPath 目标文件夹路径
     * @throws IOException IO 异常
     */
    public static void copyFolder(String sourcePath, String targetPath) throws IOException {
        File sourceFile = new File(sourcePath);
        File targetFile = new File(targetPath);

        // 1. 如果源文件不是文件夹,直接复制文件
        if (!sourceFile.isDirectory()) {
            copyFile(sourceFile, targetFile);
            return;
        }

        // 2. 创建目标文件夹
        if (!targetFile.exists()) {
            boolean mkdirsSuccess = targetFile.mkdirs();
            if (!mkdirsSuccess) {
                throw new IOException("创建目标文件夹失败:" + targetPath);
            }
        }

        // 3. 获取源文件夹下的所有文件和子文件夹
        File[] files = sourceFile.listFiles();
        if (files == null) {
            return;
        }

        // 4. 循环复制每个文件和子文件夹
        for (File file : files) {
            String newSourcePath = file.getAbsolutePath();
            String newTargetPath = targetPath + File.separator + file.getName();
            copyFolder(newSourcePath, newTargetPath);
        }
    }

    /**
     * 复制单个文件
     *
     * @param sourceFile 源文件
     * @param targetFile 目标文件
     * @throws IOException IO 异常
     */
    public static void copyFile(File sourceFile, File targetFile) throws IOException {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile));
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetFile))) {
            byte[] buffer = new byte[1024 * 8]; // 8KB 缓冲区
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
                bos.flush();
            }
        }
    }
}
1.5.3 案例测试与总结
  1. 创建一个测试文件夹,包含文本、图片、视频等多种类型的文件和子文件夹。
  2. 运行上述代码,指定源文件夹和目标文件夹路径。
  3. 检查目标文件夹,确认所有文件和子文件夹都被成功复制。

案例总结:这个工具类结合了字节流、缓冲流的核心知识,是实际开发中非常实用的功能。 通过这个案例,我们可以掌握 IO 流的嵌套使用和文件夹递归遍历的技巧。

1.6 本章总结

  1. 字节流处理所有类型文件,字符流专门处理文本文件,根据场景选择合适的流。
  2. 缓冲流能大幅提升 IO 效率,处理大文件时必须使用缓冲流。
  3. 转换流可以解决文件编码乱码问题,通过指定编码格式实现正确读写。
  4. JDK7 及以上版本优先使用 try-with-resources 语法,自动释放 IO 资源。
  5. 文件夹复制案例综合运用了 IO 流的核心知识,是提升实战能力的重要练习。

目录

  1. Java IO 流进阶:字符流与字节流的深度应用
  2. 1.1 本章学习目标与重点
  3. 1.2 字节流与字符流的核心差异
  4. 1.2.1 基本概念与设计初衷
  5. 1.2.2 代码实操:字节流读写文本文件
  6. 1.2.3 代码实操:字符流读写文本文件
  7. 1.2.4 字节流与字符流的性能对比
  8. 1.3 转换流:解决文件编码乱码问题
  9. 1.3.1 转换流的作用
  10. 1.3.2 代码实操:指定编码读取文件
  11. 1.4 IO 流资源释放的标准写法
  12. 1.4.1 JDK7 之前的写法:try-catch-finally
  13. 1.4.2 JDK7 及之后的写法:try-with-resources
  14. 1.5 实战案例:文件夹批量复制工具
  15. 1.5.1 需求分析
  16. 1.5.2 代码实现
  17. 1.5.3 案例测试与总结
  18. 1.6 本章总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 基于OpenCASCADE的分层截面几何体重建与STL导出技术
  • VS Code Copilot 完整使用教程(含图解)
  • Stable Diffusion WebUI 本地安装与配置教程
  • Spring Web MVC 入门:从概念到实战的参数处理指南
  • 专利检索与 AI 辅助工具资源汇总
  • FastAPI 高性能后端开发实战
  • 使用 Docker 和 Datmo 快速配置 AI 开发环境
  • C/C++ 动态规划入门:多状态 DP 实战(打家劫舍与股票买卖)
  • DeepSeek 深度使用指南与高效提示词技巧
  • Trae + Git 本地仓库离线管理指南
  • MySQL 与 MCP 集成:从环境构建到 AI 数据交互全流程
  • AIGC 产品经理面试高频题解析与参考答案
  • 个人健康中枢的 AI 硬件革新与精准健康管理路径
  • 微服务项目在线 OJ 系统:JWT 令牌与网关身份认证实现
  • LeetCode 206:反转链表
  • AI 大模型提示工程(Prompt)核心技巧与工具详解
  • 无人机红外照片处理中的温度校准与精度优化策略
  • VMware 中安装配置 Ubuntu 系统
  • AI 基础核心概念:Prompt、Agent、Function Calling 及 RAG 解析
  • 深度学习在正常与老年性黄斑变性 OCT 图像分类中的有效性分析

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online