广告位 · 页头(后台-模块管理-新建模块后替换此区域)

Java中如何使用FileReader和FileWriter读写文件?

FileReader和FileWriter位于java.io包,是Java字符流体系中最基础的两个类。它们分别继承自InputStreamReader和OutputStreamWriter,底层负责在字节流和字符流之间进行编码转换,操作单位是char而不是byte。这意味着它们更适合处理纯文本文件,例如配置文件、日志文件或源代码文件,而不适合处理图片、视频等二进制数据。使用FileReader读取文件时,每次read调用返回一个int值,该值是读取到的字符或-1;FileWriter的write方法则可以将字符、字符数组或字符串写入目标文件。理解这两个类的构造方式、读写行为和编码限制,是掌握Java文件操作的重要一步。

Java中如何使用FileReader和FileWriter读写文件?

一、FileReader与FileWriter的构造方法和基本读写

FileReader提供了几个常用构造方法:可以通过文件路径字符串初始化,例如new FileReader("data.txt");也可以通过File对象初始化,例如new FileReader(new File("data.txt"));还可以使用FileDescriptor表示已打开的文件描述符。FileWriter的构造方法类似,但额外提供了是否追加写入的重载,例如new FileWriter("log.txt", true)表示在文件末尾追加内容,而不是覆盖原有内容。如果目标文件不存在,FileWriter会自动创建;如果路径中包含目录,该目录必须已经存在,否则会抛出IOException。

一个需要注意的细节是,FileReader和FileWriter都没有提供指定字符编码的构造方法。它们内部使用FileInputStream和FileOutputStream,再通过InputStreamReader和OutputStreamWriter完成字节到字符的转换,编码采用平台默认值。在Windows上通常是GBK,在Linux或macOS上通常是UTF-8。因此同一段代码在不同操作系统上读写包含中文的文件,可能会得到不同结果。如果对编码有明确要求,应该使用InputStreamReader和OutputStreamWriter并指定Charset,这一点会在后文详细说明。

下面是一个使用FileReader读取文本文件并逐字符打印到控制台的例子:

import java.io.FileReader;
import java.io.IOException;
public class ReadFileExample {
    public static void main(String[] args) {
        String filePath = "data.txt";
        try (FileReader reader = new FileReader(filePath)) {
            int ch;
            while ((ch = reader.read()) != -1) {
                System.out.print((char) ch);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这段代码使用了try-with-resources语法,FileReader会在代码块执行完毕后自动调用close方法,无需手动关闭。read方法每次读取一个字符,返回int类型的值,如果到达文件末尾则返回-1。强制转换为char后即可输出。这种方式代码简单,但因为每次循环都涉及一次底层系统调用,读取大文件时效率很低。

二、使用字符数组缓冲提升读写效率

字符流读取数据时,单字符read和write方法会频繁触发底层IO操作。以读取1MB的文本文件为例,如果逐字符读取,可能需要调用read方法上百万次;每次调用都伴随系统调用和上下文切换,耗时明显增加。改进方式是一次读取多个字符放入char数组,然后批量处理。FileReader提供了read(char[] cbuf)方法,它会尝试读取足够字符填满数组,返回实际读取的字符个数,到达末尾返回-1。FileWriter则提供write(char[] cbuf, int off, int len)方法,可以一次性写出数组中的一部分字符。

下面的示例使用1024大小的字符数组完成文件复制,相比单字符版本,性能可以提升数倍到数十倍:

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyFileWithBuffer {
    public static void main(String[] args) {
        String source = "source.txt";
        String target = "target.txt";
        try (FileReader reader = new FileReader(source);
             FileWriter writer = new FileWriter(target)) {
            char[] buffer = new char[1024];
            int length;
            while ((length = reader.read(buffer)) != -1) {
                writer.write(buffer, 0, length);
            }
            System.out.println("复制完成");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

缓冲数组的大小不必固定为1024,可以根据文件大小和内存情况调整,常见的值有512、1024、2048或8192。数组越大,每次读取的数据越多,系统调用次数越少,但占用的内存也越多。对于普通文本文件,1024或4096通常是性能和内存之间的较好平衡。此外,Java还提供了BufferedReader和BufferedWriter两个装饰器类,它们在内部维护缓冲区,并提供readLine()和newLine()等便捷方法,适合按行处理文本。

例如,要按行读取文件内容,可以直接将FileReader包装到BufferedReader中:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class ReadLineExample {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

BufferedReader默认缓冲区大小为8192个字符,也可以自定义。对于需要频繁写入的场景,可以使用BufferedWriter包裹FileWriter,调用write(String)后数据先进入缓冲区,缓冲区满或调用flush/close时才真正写入磁盘,从而减少写盘次数。需要注意的是,写入结束后必须调用flush或close,否则缓冲区中的数据可能丢失。

三、编码限制与异常处理

FileReader和FileWriter最常被忽视的问题是无法指定编码。如果源文件的编码与平台默认编码不一致,读取到的字符串就会乱码。例如在Windows平台下使用FileReader读取一个UTF-8编码的中文文件,就可能出现乱码。解决方法是使用InputStreamReader或OutputStreamWriter显式指定字符集。这两个类需要传入一个InputStream或OutputStream,以及一个Charset对象或字符串名称:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
public class ReadWriteWithEncoding {
    public static void main(String[] args) {
        String source = "utf8.txt";
        String target = "copy_utf8.txt";
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(new FileInputStream(source), StandardCharsets.UTF_8));
             BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(new FileOutputStream(target), StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                writer.write(line);
                writer.newLine();
            }
            System.out.println("按UTF-8编码复制完成");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这段代码使用FileInputStream和FileOutputStream作为字节流基础,再通过InputStreamReader和OutputStreamWriter指定UTF-8编码,外层包裹BufferedReader和BufferedWriter获得按行读写和缓冲能力。相比直接使用FileReader和FileWriter,这种组合更灵活,适合对编码有明确要求的场景。

资源管理方面,推荐始终使用try-with-resources语法。它会自动调用资源的close方法,即使发生异常也能保证文件被关闭。try语句中可以声明多个资源,用分号分隔,关闭顺序与声明顺序相反。如果不使用try-with-resources,就需要在finally块中手动关闭,代码会显得冗长且容易遗漏。此外,操作文件时经常遇到FileNotFoundException和IOException,前者表示文件不存在或路径无效,后者表示读写过程中发生IO错误,例如磁盘空间不足或权限不足。捕获异常后应根据业务需要进行日志记录或用户提示,而不是简单地忽略。

路径书写也需要注意。在Windows系统中,路径分隔符为反斜杠,例如C:\Users\test\data.txt。但在Java字符串中,反斜杠本身是转义字符,因此需要写成"C:\\Users\\test\\data.txt"。更推荐的做法是使用File.separator常量或正斜杠,因为Java在Windows上也接受正斜杠作为路径分隔符,例如"C:/Users/test/data.txt",这样代码更具可移植性。

四、FileReader/FileWriter与字节流的对比及选择建议

很多Java初学者容易把FileReader和FileInputStream混为一谈。两者虽然都用于读取文件,但FileInputStream是字节流,读取的是原始字节,不进行任何编码转换;FileReader是字符流,读取的是字符,内部自动完成字节到字符的转换。举个例子,读取UTF-8编码的中文文件时,中文通常占3个字节,FileInputStream一次read只能读取单个字节,需要自行拼接并处理编码;而FileReader一次read就能返回一个完整字符,使用更方便。但如果读取的是图片、视频、压缩包等二进制文件,字符流会尝试对字节进行编码解码,可能会损坏数据,因此必须使用字节流。

从性能和功能角度看,FileReader和FileWriter适合处理小型文本文件或快速原型开发,代码简单直接。对于大文件、需要指定编码、需要按行处理或需要高效读写的场景,应该优先使用BufferedReader、BufferedWriter配合InputStreamReader、OutputStreamWriter,或者使用更高层次的Files工具类。Java NIO中的Files.readAllLines(Path, Charset)和Files.write(Path, Iterable, Charset)也能快速完成小文件的整体读写,并且支持指定编码,是更现代的替代方案。

总结来说,FileReader和FileWriter作为Java IO的基础组件,理解它们的工作机制和局限性,有助于灵活选择适合当前需求的API。如果只是读取一个几KB的配置文件,用FileReader足够;如果需要按行处理几十MB的日志文件,最好使用BufferedReader并指定编码;如果操作二进制数据,则直接使用FileInputStream和FileOutputStream。掌握这些差异,可以避免乱码和性能陷阱,写出更健壮的文件处理代码。

广告位 · 文章底部(后台-模块管理替换此区域)