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<String> contents = new ArrayList<>();
        contents.add("test1");
        contents.add("test2");
        write("D:\\test.txt", contents);
    }

    private static void write(String fileName, List<String> 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<String> contents = new ArrayList<>();
        contents.add("test1");
        contents.add("test2");
        write("D:\\test.txt", contents);
    }

    private static void write(String fileName, List<String> 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<String> contents = new ArrayList<>();
        contents.add("test1");
        contents.add("test2");
        write("D:\\test.txt", contents);
    }

    private static void write(String fileName, List<String> 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<String> contents = new ArrayList<>();
        contents.add("test1");
        contents.add("test2");
        writ("D:\\test.txt", contents);
    }

    private static void writ(String fileName, List<String> 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<String> 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<String> contents = new ArrayList<>();
        contents.add("test1");
        contents.add("test2");
        write("D:\\test.txt", contents);
    }

    private static void write(String fileName, List<String> 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<String> 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<String> 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<String> 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 语句(如示例中所示),确保流会被正确关闭,避免资源泄漏。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值