Java文件与IO流详细攻略

 更新时间:2025年11月11日 10:48:16   作者:无名-CODING  
文章详细介绍了Java中的IO流,包括字节流和字符流的区别,如何使用File类进行文件操作,以及如何进行高效的文件读写,文章还讨论了字符编码的重要性,如何避免常见的编码问题,以及如何使用现代的NIO.2 API进行文件操作,感兴趣的朋友跟随小编一起看看吧

导读:为什么要学 IO 流?

  • 程序要与“外部世界”交互——读写文件、网络传输、控制台输入输出,本质都是“数据流”。
  • Java 提供了两大类 IO:
    • 字节流InputStream / OutputStream(处理二进制:图片、音频、视频、任意文件)
    • 字符流Reader / Writer(处理文本:txt、md、json、java 源码等)
  • 记忆口诀:
    • “看得懂的是字符流(文本),看不懂的是字节流(二进制)”。
    • “文本优先用字符流,其他都用字节流,拿不准选字节流”。

基石:File 类(文件与目录)

java.io.File 不是“文件内容”,而是“文件或目录的路径抽象”。常见用法:

import java.io.File;
public class FileBasicsDemo {
    public static void main(String[] args) {
        // 1) 路径与构造
        File file = new File("D:/IO/hello.txt"); // 建议使用 / 或者使用 Paths 构建
        File dir  = new File("D:/IO/sub");
        // 2) 基本信息
        System.out.println(file.getAbsolutePath());
        System.out.println(file.getName());
        System.out.println(file.exists());
        System.out.println(file.isFile());
        System.out.println(file.isDirectory());
        // 3) 创建目录/文件
        if (!dir.exists()) {
            boolean ok = dir.mkdirs(); // 递归创建目录
            System.out.println("mkdirs: " + ok);
        }
        // 创建新文件(若父目录不存在会失败)
        try {
            if (!file.exists()) {
                boolean created = file.createNewFile();
                System.out.println("createNewFile: " + created);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 4) 遍历目录
        File root = new File("D:/IO");
        File[] children = root.listFiles();
        if (children != null) {
            for (File f : children) {
                System.out.println((f.isDirectory() ? "[DIR] " : "[FILE]") + f.getName());
            }
        }
    }
}

提示:在 Windows 上用 "D:/path/file.txt" 更稳妥,避免 \\ 转义麻烦。生产代码推荐 java.nio.file.PathsFiles(NIO.2),本文以 IO 基础为主。

两大体系的核心区别

  • 字节流(8-bit)InputStream / OutputStream,按“字节”处理,适合任何数据;
  • 字符流(16-bit)Reader / Writer,按“字符”处理,关注“编码”,适合文本;
  • 关系:字符流往往是“基于字节流 + 编码解码器(InputStreamReader / OutputStreamWriter)”。

字符流(Reader/Writer):处理文本最顺手

1)字符输入:Reader 家族

  • Reader:抽象基类
  • 常见实现:
    • FileReader:快捷读取文件文本(使用平台默认编码,不建议在生产环境依赖默认编码)
    • InputStreamReader:桥接器,将字节流解码为字符流,可指定编码(推荐)
    • BufferedReader:带缓冲、支持 readLine(),高效读取

修正与提升后的综合示例(包含三种读取方式,改为 try-with-resources、指定编码、健壮性更好):

import java.io.*;
import java.nio.charset.StandardCharsets;
public class ReaderDemo {
    public static void main(String[] args) {
        String path = "D:/IdeaProjects/JavaTest/src/com/qcby/a.txt";
        // 1) FileReader:简单,但依赖平台默认编码(不推荐在生产使用)
        try (FileReader reader = new FileReader(path)) {
            int ch;
            while ((ch = reader.read()) != -1) {
                System.out.print((char) ch);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 2) InputStreamReader:可指定编码(推荐)
        try (InputStreamReader reader = new InputStreamReader(
                new FileInputStream(path), StandardCharsets.UTF_8)) {
            int ch;
            while ((ch = reader.read()) != -1) {
                System.out.print((char) ch);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 3) BufferedReader:高效且支持按行读取
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

说明:FileReaderFileWriter 在编码未知或跨平台时不够稳妥,实际开发更推荐 InputStreamReader/OutputStreamWriter 并显式指定编码。

2)字符输出:Writer 家族

  • Writer:抽象基类
  • 常见实现:
    • FileWriter:快捷写文本(默认编码,不建议长期依赖)
    • OutputStreamWriter:桥接器,将字符流编码为字节流,可指定编码(推荐)
    • BufferedWriter:带缓冲、newLine() 友好换行

综合示例(修正:使用 try-with-resources,必要时 flush/close,路径与编码更明确):

import java.io.*;
import java.nio.charset.StandardCharsets;
public class WriterDemo {
    public static void main(String[] args) {
        String outPath = "D:/IO/output.txt";
        // 1) FileWriter:简单写文本
        try (FileWriter writer = new FileWriter(outPath)) { // 默认编码
            writer.write("Hello, World!\n");
            writer.write("This is a new line.\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 2) OutputStreamWriter:指定编码(推荐)
        try (OutputStreamWriter writer = new OutputStreamWriter(
                new FileOutputStream(outPath, true), StandardCharsets.UTF_8)) { // 追加写
            writer.write("追加一行,使用UTF-8编码。\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 3) BufferedWriter:高效按行写
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(outPath, true))) {
            bw.write("Hello, World!");
            bw.newLine();
            bw.write("This is a new line.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3)Writer 的五种write方法(快速对照)

  • write(int c):写入单个字符(低 16 位有效)
  • write(char[] cbuf):写入字符数组
  • write(char[] cbuf, int off, int len):写入字符数组的一部分
  • write(String str):写入字符串
  • write(String str, int off, int len):写入字符串的一部分

示例(修正笔记,补全导入与路径):

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class WriterExample {
    public static void main(String[] args) {
        String out = "D:/IO/writer-demo.txt";
        try (Writer writer = new FileWriter(out)) {
            // 1. 写入单个字符
            writer.write('H');
            // 2. 写入字符数组
            char[] array = {'e', 'l', 'l', 'o'};
            writer.write(array);
            // 3. 写入字符数组的一部分(写入 "ll")
            writer.write(array, 2, 2);
            // 4. 写入字符串
            writer.write(", World!");
            // 5. 写入字符串的一部分(写入 "This is Java")
            String str = "\nThis is Java IO.";
            writer.write(str, 1, 12);
            System.out.println("数据已写入文件!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

字节流(InputStream/OutputStream):万能读写器

1)字节输入:InputStream 家族

  • 核心方法:
    • int read():读 1 字节(0-255),EOF 返回 -1
    • int read(byte[] b):批量读取到数组,返回本次读取长度或 -1
    • int read(byte[] b, int off, int len):从 off 开始最多读 len 个字节
    • void close():关闭流并释放资源
  • 常见实现:FileInputStreamBufferedInputStreamByteArrayInputStream

基于笔记修正后的示例:

import java.io.*;
import java.nio.charset.StandardCharsets;
public class FileInputStreamTest {
    public static void main(String[] args) {
        File file = new File("D:/IO/hello.txt");
        // 建议:优先使用 try-with-resources,避免手动 finally 关闭
        try (FileInputStream fis = new FileInputStream(file)) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                // 如果是文本,可按编码构造字符串;若是二进制,直接处理字节
                String chunk = new String(buffer, 0, len, StandardCharsets.UTF_8);
                System.out.print(chunk);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意:如果文件不是 UTF-8 文本,上面把字节解码为字符串可能出现乱码。处理图片/音频/视频等二进制文件时,不要把字节强行转成字符串。

2)字节输出:OutputStream 家族

  • 核心方法:
    • void write(int b):写 1 个字节(低 8 位有效)
    • void write(byte[] b):写入整个字节数组
    • void write(byte[] b, int off, int len):写入数组的一部分
    • void flush():刷新缓冲区(有缓冲的输出流/Writer)
    • void close():关闭流并释放资源
  • 常见实现:FileOutputStreamBufferedOutputStreamByteArrayOutputStream

增强示例(追加写入、换行、片段写入):

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class FileOutputStreamTest {
    public static void main(String[] args) {
        File file = new File("D:/IO/hello.txt");
        // true 表示追加写
        try (FileOutputStream fos = new FileOutputStream(file, true)) {
            fos.write(97); // 写入单字节 'a'
            fos.write("\r\n".getBytes(StandardCharsets.UTF_8));
            fos.write("中国人!\r\n".getBytes(StandardCharsets.UTF_8));
            fos.write("ABCDEFGH".getBytes(StandardCharsets.UTF_8), 2, 4); // 输出 CDEF
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Windows 的换行是 \r\n,Linux/Unix 是 \n。跨平台建议使用 System.lineSeparator()BufferedWriter.newLine()

性能与最佳实践

  • 使用缓冲:BufferedInputStream / BufferedOutputStream / BufferedReader / BufferedWriter
  • 大块读写:优先 byte[] / char[] 缓冲区读写,减少系统调用
  • 明确编码:文本读写显式指定 Charset(如 StandardCharsets.UTF_8
  • 及时关闭:使用 try-with-resources 自动关闭(JDK 7+)
  • 追加写入:输出构造器带 append = true,如 new FileOutputStream(path, true)
  • 避免小而频繁的 read()/write():合并为批量操作

示例:拷贝任意文件(字节流 + 缓冲 + 大块读写)

import java.io.*;
public class FileCopy {
    public static void copyFile(String src, String dest) throws IOException {
        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(src));
             BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(dest))) {
            byte[] buf = new byte[8192];
            int len;
            while ((len = in.read(buf)) != -1) {
                out.write(buf, 0, len);
            }
        }
    }
    public static void main(String[] args) throws IOException {
        copyFile("D:/IO/source.bin", "D:/IO/target.bin");
    }
}

字符编码与常见坑

  • FileReader/FileWriter 使用“平台默认编码”,在不同机器/IDE/系统下可能不同,易乱码。
  • 推荐始终显式使用编码:
    • 读:new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8)
    • 写:new OutputStreamWriter(new FileOutputStream(path), StandardCharsets.UTF_8)
  • 文本文件中若包含表情/罕见字符,优先 UTF-8(覆盖更全)。

什么时候用字节流?什么时候用字符流?

  • “只要涉及文本内容且你需要‘字符’语义(如按行、按字符处理),用字符流”。
  • “不确定是不是文本、或需要按原始字节处理(如复制图片/压缩包/视频),用字节流”。
  • “需要指定编码(文本跨平台),用 InputStreamReader/OutputStreamWriter 代替 FileReader/FileWriter”。

 实战:综合示例(读取一个 UTF-8 文本,转换后写入新文件)

import java.io.*;
import java.nio.charset.StandardCharsets;
public class TextTransformDemo {
    public static void main(String[] args) {
        String src = "D:/IO/input-utf8.txt";
        String dest = "D:/IO/output-utf8.txt";
        try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(src), StandardCharsets.UTF_8));
             BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(dest), StandardCharsets.UTF_8))) {
            String line;
            int lineNo = 1;
            while ((line = br.readLine()) != null) {
                // 简单转换:在每行前加上行号
                bw.write(String.format("%04d: %s", lineNo++, line));
                bw.newLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 进阶一瞥:NIO 与 Files(了解即可)

  • java.nio.file.Files/Paths 提供更现代的 API:
    • Files.readAllLines(path, charset) 读所有行
    • Files.write(path, bytes, options...) 写入字节
    • Files.copy/Files.move/Files.delete 文件操作
  • FileChannelMappedByteBuffer 可进行高性能文件读写(大文件、零拷贝场景)。

常见错误与改正

  • 错误:在 try 外创建流,在 finallyclose() 时忽略 null 判断
    修正:使用 try-with-resources 自动关闭。
  • 错误:文本使用字节流读取后直接 new String(bytes) 未指定编码
    修正:显式使用 Charset;或改用字符流。
  • 错误:频繁 read()/write() 单字节
    修正:使用缓冲流 + 批量读写。
  • 错误:路径用 C:\\a\\b.txt 忘记转义
    修正:改用正斜杠 C:/a/b.txtPaths.get("C:", "a", "b.txt")

面试常见问题与答案

1)为什么有了字节流还需要字符流?

  • 字节流面向原始数据,不关心编码;字符流关注“字符语义”,内置解码/编码,更适合文本操作(按行、按字符)。

2)FileReaderInputStreamReader 区别?

  • FileReader 是简化版字符输入,使用平台默认编码;InputStreamReader 是“字节→字符”的桥接器,可显式指定编码,跨平台更可靠,推荐。

3)BufferedReader.readLine() 为什么常用?

  • 带缓冲、按行读取,避免频繁系统调用,性能更好;并且直接得到“行”这个文本级别的语义单位。

4)hashCode/equals 和 IO 有关系吗?

  • 直接关系不大,但在使用 Set/Map 统计文件名、缓存流对象时会涉及相等性与散列。IO 场景更多关注性能、正确编码与资源释放。

5)如何高效复制大文件?

  • 使用 BufferedInputStream + BufferedOutputStream + 大缓冲区(8KB~1MB),或使用 NIO 的 Files.copy/FileChannel.transferTo/transferFrom

6)什么时候需要 flush()

  • 对“带缓冲”的输出流/Writer,在你希望“立即”写出数据时(如网络/日志/交互式输出),或程序即将结束但未关闭流时需要 flush()

7)为什么 Windows 下是 \r\n 换行?

  • 历史原因;跨平台代码建议使用 System.lineSeparator()BufferedWriter.newLine() 来统一处理。

8)读取二进制文件时出现乱码怎么办?

  • 不要把二进制“按字符”解码,直接按字节处理;只有在确定编码并且是文本时,才使用字符流或按正确编码构造字符串。

9)FileFiles 的区别?

  • File 是老 IO 的路径抽象,Files 是 NIO.2 的工具类,提供更丰富、更现代的文件操作 API。

10)try-with-resources 的底层原理?

  • 编译器语法糖:会在编译期生成 try { ... } finally { resource.close(); } 结构,并按“逆序”关闭多个资源。

小结

  • 分清字节流与字符流,记住“文本=字符流,其他=字节流(拿不准选字节流)”。
  • 文本必须重视编码,推荐 UTF-8,用 InputStreamReader/OutputStreamWriter 显式指定。
  • 性能优化三件套:缓冲、批量读写、try-with-resources。
  • File 只代表路径,现代项目逐步拥抱 NIO.2(Paths/Files)。

到此这篇关于Java文件与IO流详细攻略的文章就介绍到这了,更多相关Java文件与IO流内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java简单统计字符串中汉字,英文字母及数字数量的方法

    Java简单统计字符串中汉字,英文字母及数字数量的方法

    这篇文章主要介绍了Java简单统计字符串中汉字,英文字母及数字数量的方法,涉及java针对字符串的遍历、编码转换、判断等相关操作技巧,需要的朋友可以参考下
    2017-06-06
  • Springboot中LocalDateTime对象返回给前端格式化解决方案

    Springboot中LocalDateTime对象返回给前端格式化解决方案

    在项目开发当中前后端使用什么样的时间格式,是一个值得关注的问题,这篇文章主要给大家介绍了关于Springboot中LocalDateTime对象返回给前端格式化的解决方案,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-04-04
  • Mybatis实现增删改查及分页查询的方法

    Mybatis实现增删改查及分页查询的方法

    MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持 久层框架,通过本文给大家介绍Mybatis实现增删改查及分页查询的方法,感兴趣的朋友一起学习吧
    2016-01-01
  • Mybatis通过Mapper代理连接数据库的方法

    Mybatis通过Mapper代理连接数据库的方法

    这篇文章主要介绍了Mybatis通过Mapper代理连接数据库的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-11-11
  • Java NIO 底层原理解析

    Java NIO 底层原理解析

    Java IO读写通过系统调用在内核与进程缓冲区间传输数据,不直接操作磁盘,本文给大家介绍Java NIO 底层原理解析,感兴趣的朋友一起看看吧
    2025-07-07
  • Spring Boot Actuator 漏洞利用小结

    Spring Boot Actuator 漏洞利用小结

    spring对应两个版本,分别是Spring Boot 2.x和Spring Boot 1.x,因此后面漏洞利用的payload也会有所不同,这篇文章主要介绍了Spring Boot Actuator 漏洞利用小结,需要的朋友可以参考下
    2023-11-11
  • Springboot接口日志加入链路追踪traceId方式

    Springboot接口日志加入链路追踪traceId方式

    文章介绍通过添加依赖、配置logback-spring.xml、使用@LogTrace注解和LogTraceAspect切面实现请求日志追踪,测试时日志中可显示traceId
    2025-08-08
  • 详解SpringBoot下文件上传与下载的实现

    详解SpringBoot下文件上传与下载的实现

    这篇文章主要介绍了SpringBoot下文件上传与下载的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • vue3使用vue-diff工具来比较数据差异

    vue3使用vue-diff工具来比较数据差异

    这篇文章主要为大家详细介绍了vue3如何使用vue-diff工具来比较数据差异,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-11-11
  • Java中连接数据库方式详细步骤记录

    Java中连接数据库方式详细步骤记录

    这篇文章主要介绍了Java中连接数据库方式的详细步骤,包括添加依赖、建立连接、执行SQL语句、处理结果集和关闭连接,还讨论了数据库连接池的使用,需要的朋友可以参考下
    2025-01-01

最新评论