> 技术文档 > Java 文本文件读写全面指南:从经典 IO 到现代 NIO 的演进与实践

Java 文本文件读写全面指南:从经典 IO 到现代 NIO 的演进与实践


一、经典 IO (java.io 包)

这是最传统和广为人知的方式,核心是 Reader 和 Writer 这两个抽象类,用于处理字符文本。

1. FileReader / FileWriter

这是最基础的实现,用于直接读写文件

写文件示例

import java.io.FileWriter;import java.io.IOException;import java.util.ArrayList;import java.util.List;public class Demo { public static void main(String[] args) { List contents = new ArrayList(); contents.add(\"test1\"); contents.add(\"test2\"); write(\"D:\\\\test.txt\", contents); } private static void write(String fileName, List contents) { try (FileWriter writer = new FileWriter(fileName)) { for (String content : contents) { writer.write(content + \"\\n\"); } } catch (IOException e) { e.printStackTrace(); } }}

读文件示例:

import java.io.FileReader;import java.io.IOException;public class Demo { public static void main(String[] args) { read(\"D:\\\\test.txt\"); } private static void read(String fileName) { try (FileReader reader = new FileReader(fileName)) { int charCode; while ((charCode = reader.read()) != -1) { System.out.print((char) charCode); } } catch (IOException e) { e.printStackTrace(); } }}
  • 优点:

    • API 简单直观,易于学习和使用。

  • 缺点:

    • 性能极差FileReader 的 read() 方法是逐字符读取的,会产生大量的系统调用。

    • 功能单一:没有缓冲区,不支持按行读取等高级功能。

    • 编码问题:使用平台默认的字符编码(如 Windows 上是 GBK),无法指定编码,容易导致乱码。这是最大的坑。

  • 适用场景:

    • 基本不用。除非是处理非常小的、简单的、且编码与系统默认一致的文件,否则不推荐使用。

2. BufferedReader / BufferedWriter

在基础流之上包装了缓冲区,是最常用和经典的高效文本处理方式。

写文件示例 (BufferedWriter):

import java.io.BufferedWriter;import java.io.FileWriter;import java.io.IOException;import java.util.ArrayList;import java.util.List;public class Demo { public static void main(String[] args) { List contents = new ArrayList(); contents.add(\"test1\"); contents.add(\"test2\"); write(\"D:\\\\test.txt\", contents); } private static void write(String fileName, List contents) { try (FileWriter fw = new FileWriter(fileName); BufferedWriter writer = new BufferedWriter(fw)) { for (String content : contents) { writer.write(content); writer.newLine(); // 换行,平台无关,比写 \"\\n\" 更好 } } catch (IOException e) { e.printStackTrace(); } }}

读文件示例 (BufferedReader):

import java.io.BufferedReader;import java.io.FileReader;import java.io.IOException;public class Demo { public static void main(String[] args) { read(\"D:\\\\test.txt\"); } private static void read(String fileName) { try (FileReader fr = new FileReader(fileName); BufferedReader reader = new BufferedReader(fr)) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } }}
  • 优点:

    • 性能高:内置缓冲区(默认8KB),大大减少了底层系统的读写次数。

    • 功能强大BufferedReader.readLine() 是处理文本文件的神器,非常适合逻辑按行处理的场景。

  • 缺点:

    • 仍然依赖于 FileReader/FileWriter,因此默认编码问题依然存在

  • 适用场景:

    • 需要按行处理文本内容的绝大多数情况(如日志分析、数据文件读取)。

    • 读写普通大小的文本文件。是 Java 7 之前事实上的标准。

3. InputStreamReader / OutputStreamWriter (解决编码问题的关键)

这是桥梁,用于将字节流转换为字符流,并且可以显式指定字符编码

写文件示例 (指定UTF-8编码):

import java.io.BufferedWriter;import java.io.FileOutputStream;import java.io.IOException;import java.io.OutputStreamWriter;import java.nio.charset.StandardCharsets;import java.util.ArrayList;import java.util.List;public class Demo { public static void main(String[] args) { List contents = new ArrayList(); contents.add(\"test1\"); contents.add(\"test2\"); write(\"D:\\\\test.txt\", contents); } private static void write(String fileName, List contents) { try (FileOutputStream fos = new FileOutputStream(fileName); OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8); // 指定编码 BufferedWriter writer = new BufferedWriter(osw)) { // 再用Buffer包装,提升性能 for (String content : contents) { writer.write(content); writer.newLine(); } } catch (IOException e) { e.printStackTrace(); } }}

读文件示例 (指定UTF-8编码):

import java.io.BufferedReader;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStreamReader;import java.nio.charset.StandardCharsets;public class Demo { public static void main(String[] args) { read(\"D:\\\\test.txt\"); } private static void read(String fileName) { try (FileInputStream fis = new FileInputStream(fileName); InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8); // 指定编码 BufferedReader reader = new BufferedReader(isr)) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } }}
  • 优点:

    • 解决了编码问题:可以明确指定字符集(如 UTF-8, GBK),确保跨环境一致性,避免乱码。

    • 灵活性高,可以对接任何字节流。

  • 缺点:

    • 用法比 FileReader/Writer 稍复杂一些。

  • 适用场景:

    • 所有需要处理编码的文本读写场景。这是处理文本文件的最佳实践组合BufferedReader + InputStreamReader + FileInputStream

4. PrintWriter

提供了丰富的打印格式方法,如 print()println()printf()

写文件示例:

import java.io.FileWriter;import java.io.IOException;import java.io.PrintWriter;import java.util.ArrayList;import java.util.List;public class Demo { public static void main(String[] args) { List contents = new ArrayList(); contents.add(\"test1\"); contents.add(\"test2\"); writ(\"D:\\\\test.txt\", contents); } private static void writ(String fileName, List contents) { try (FileWriter fw = new FileWriter(fileName); PrintWriter writer = new PrintWriter(fw)) { // 也可以直接包装 FileOutputStream for (String content : contents) { writer.println(content); } } catch (IOException e) { e.printStackTrace(); } }}
  • 优点:

    • 输出格式非常方便,类似于 System.out

    • 可以自动刷新缓冲区(可选功能)。

  • 缺点:

    • 异常处理 silent:它的方法不会抛出 IOException,需要通过 checkError() 方法自己检查错误状态,容易让人忽略错误。

  • 适用场景:

    • 需要向文件或输出流中写入格式化文本,比如生成报告、数据文件。

二、NIO 和 NIO.2 (java.nio.file 包)

Java 7 引入了 NIO.2,提供了更现代、更强大的文件操作 API。

1. Files 工具类 (一次性读写)

Files 类提供了一系列静态方法,可以用一行代码完成整个文件的读写,极其方便。

读文件示例 (读取所有行到List):

import java.io.IOException;import java.nio.charset.StandardCharsets;import java.nio.file.Files;import java.nio.file.Paths;import java.util.List;public class Demo { public static void main(String[] args) { read(\"D:\\\\test.txt\"); } private static void read(String fileName) { try { // 读取所有行,默认编码是 UTF-8 List lines = Files.readAllLines(Paths.get(fileName)); for (String line : lines) { System.out.println(line); } // 读取整个文件为字节数组(适用于任何文件,包括文本) byte[] bytes = Files.readAllBytes(Paths.get(fileName)); System.out.println(new String(bytes, StandardCharsets.UTF_8)); } catch (IOException e) { e.printStackTrace(); } }}

写文件示例:

import java.io.IOException;import java.nio.charset.StandardCharsets;import java.nio.file.Files;import java.nio.file.Paths;import java.util.ArrayList;import java.util.List;public class Demo { public static void main(String[] args) { List contents = new ArrayList(); contents.add(\"test1\"); contents.add(\"test2\"); write(\"D:\\\\test.txt\", contents); } private static void write(String fileName, List contents) { try { // 将行的集合写入文件 Files.write(Paths.get(fileName), contents, StandardCharsets.UTF_8); // 将字节数组写入文件 Files.write(Paths.get(fileName), \"Hello Bytes\".getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { e.printStackTrace(); } }}
  • 优点:

    • 极其简洁:一行代码搞定,无需关心流的打开和关闭。

    • 功能强大:支持指定编码、文件打开选项(如追加、创建等)。

  • 缺点:

    • 不适合大文件:它会一次性将整个文件内容加载到内存中,如果文件非常大(如几个GB),会导致内存溢出(OOM)。

  • 适用场景:

    • 读写小文本文件(如配置文件、小规模数据文件)。

    • 追求代码简洁和可读性的场景。

2. Files.newBufferedReader / Files.newBufferedWriter (NIO 的流式处理)

Files 类也提供了方法来创建基于 NIO Path 的 BufferedReader 和 BufferedWriter

示例:

import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.IOException;import java.nio.charset.StandardCharsets;import java.nio.file.Files;import java.nio.file.Paths;import java.nio.file.StandardOpenOption;import java.util.ArrayList;import java.util.List;public class Demo { public static void main(String[] args) { read(\"D:\\\\test.txt\"); List contents = new ArrayList(); contents.add(\"test1\"); contents.add(\"test2\"); write(\"D:\\\\test.txt\", contents); } private static void read(String fileName) { // 读 try (BufferedReader reader = Files.newBufferedReader(Paths.get(fileName), StandardCharsets.UTF_8)) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } private static void write(String fileName, List contents) { // 写 (支持OpenOption,比如StandardOpenOption.APPEND追加,CREATE创建等) try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(fileName), StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) { for (String content : contents) { writer.write(content); writer.newLine(); } } catch (IOException e) { e.printStackTrace(); } }}
  • 优点:

    • 结合了传统 BufferedReader/Writer 的高效和 NIO.2 的现代 API(直接使用 Path 和指定编码更简单)。

    • 支持丰富的文件打开选项。

    • 适用于大文件,因为是流式处理,内存友好。

  • 缺点:

    • 相比 Files.readAllLines,代码量稍多。

  • 适用场景:

    • 处理大文本文件,需要流式、按行读取。

    • 需要向文件追加内容等更复杂的操作。

3. Java 8 Stream API (函数式处理)

结合 Files.lines() 方法,可以用声明式的流式 API 来处理文本。

示例:

import java.io.IOException;import java.nio.charset.StandardCharsets;import java.nio.file.Files;import java.nio.file.Paths;import java.util.stream.Stream;public class Demo { public static void main(String[] args) { read(\"D:\\\\test.txt\"); } private static void read(String fileName) { try (Stream lines = Files.lines(Paths.get(fileName), StandardCharsets.UTF_8)) { lines.filter(line -> line.contains(\"test\")) // 过滤出包含\"test\"的行  .map(String::toUpperCase)  // 转换为大写  .forEach(System.out::println); // 打印每一行 } catch (IOException e) { e.printStackTrace(); } }}
  • 优点:

    • 代码非常优雅,符合函数式编程思想。

    • 可以轻松地进行过滤、映射、收集等复杂操作。

    • 背后也是缓冲读取,性能好且内存友好(流是惰性求值的)。

  • 缺点:

    • 需要熟悉 Java 8 Stream 的概念。

    • I/O 异常在 Lambda 表达式中处理起来不太方便。

  • 适用场景:

    • 需要对文本内容进行复杂的查找、过滤、转换、统计等操作。

    • 追求现代、简洁、声明式的代码风格。

三、实用工具类

Scanner (主要用于读取结构化输入)

虽然常用于 System.in,但也可以用于读取文件。

示例:

import java.io.File;import java.io.FileNotFoundException;import java.util.Scanner;public class Demo { public static void main(String[] args) { read(\"D:\\\\test.txt\"); } private static void read(String fileName) { try (Scanner scanner = new Scanner(new File(fileName), \"UTF-8\")) { while (scanner.hasNextLine()) { String line = scanner.nextLine(); System.out.println(line); } // 或者按分隔符(如空格)读取 // while (scanner.hasNext()) { String token = scanner.next(); } } catch (FileNotFoundException e) { e.printStackTrace(); } }}
  • 优点:

    • 可以按正则表达式或自定义分隔符解析文本,非常适合读取结构化的数据(如 CSV)。

  • 缺点:

    • 性能通常比 BufferedReader 差。

  • 适用场景:

    • 读取格式已知的、结构化的文本数据(如由空格、逗号分隔的数据文件)。

选择指南

实现方式 优点 缺点 适用场景 FileReader/Writer 简单 性能差、编码依赖系统默认(坑) 不推荐使用 BufferedReader/Writer
(包装 FileReader/Writer) 性能高、支持按行读 编码依赖系统默认 Java7前处理普通文件的标配 BufferedReader/Writer
(包装 InputStreamReader/Writer) 性能高、可指定编码 代码稍多 处理编码、大文件的标准答案,兼容性好 PrintWriter 格式化输出方便 异常处理 silent生成格式化报告、数据文件 Files.readAllLines/write 极其简洁、功能丰富 内存消耗大 读写小文件、配置文件 Files.newBufferedReader/Writer 现代API、指定编码、支持选项、内存友好 处理大文件、需要追加等操作 Files.lines() + Stream API 声明式编程、处理逻辑强大 需熟悉Stream API

对内容进行复杂查询、过滤、转换

Scanner 解析结构化数据能力强 性能相对较低 读取CSV、空格分隔等结构化文本

建议:

  1. 处理小文件(<几十MB):直接使用 Files.readAllLines() 或 Files.write(),代码最简洁。

  2. 处理大文件或需要控制内存:使用 Files.newBufferedReader() 或 Files.newBufferedWriter(),或者传统的 BufferedReader + InputStreamReader 组合。这是最稳健、最通用的方法。

  3. 需要对文本进行复杂处理:使用 Files.lines().stream(),利用 Stream API 的强大功能。

  4. 读取结构化数据:考虑使用 Scanner

  5. 永远明确指定字符编码(如 StandardCharsets.UTF_8),不要依赖平台默认值。

  6. 始终使用 try-with-resources 语句(如示例中所示),确保流会被正确关闭,避免资源泄漏。