52、Java文件流操作:读写文本文件全解析

Java文件流操作:读写文本文件全解析

1. 理解流的概念

流是程序与外部之间字符的流动通道。流的另一端可以是控制台窗口、打印机、磁盘文件或者其他程序。流本身并不关心数据的结构和含义,仅仅是字符的序列。在Java中,流I/O大致可分为字符流和二进制流两类:
- 字符流 :用于读写代表字符串的文本字符。可以将字符流连接到文本文件,把文本数据存储在磁盘上。文本文件通常使用特殊字符(分隔符)来分隔文件中的元素,例如逗号分隔文件使用逗号分隔数据字段,制表符分隔文件使用制表符分隔字段。一般可以在文本编辑器中查看文本文件并理解其内容。
- 二进制流 :用于读写代表基本数据类型的单个字节。可以将二进制流连接到二进制文件,在磁盘上存储二进制数据。二进制文件的内容对于读写它们的程序来说是有意义的,但如果在文本编辑器中打开,内容看起来会像乱码。

2. 读取字符流

要通过字符流读取文本文件,通常会用到以下几个类:
- File :代表磁盘上的文件,在文件I/O应用中,主要用于标识要读取或写入的文件。
- FileReader :提供从文件字符流中读取数据的基本方法,允许一次读取一个字符。不过通常不会直接使用这个类,而是创建一个FileReader对象将程序与文件连接起来,然后将该对象传递给BufferedReader类的构造函数。
- BufferedReader :“包装”在FileReader类周围,提供更高效的输入。它为输入流添加了缓冲区,允许从磁盘以大块方式读取输入,而不是一次读取一个字节,这可以显著提高性能。该类允许一次读取一个字符或一行数据。在大多数程序中,会一次读取一行数据,然后使用Java的字符串处理功能将行拆分为各个字段。

构造函数 描述
BufferedReader(Reader in) 创建一个从任何扩展Reader类的对象的缓冲读取器,通常传递一个FileReader对象
FileReader(File file) 从指定的File对象创建一个文件读取器,如果文件不存在或它是一个目录而不是文件,会抛出FileNotFoundException
FileReader(String path) 从指定的路径名创建一个文件读取器,如果文件不存在或它是一个目录而不是文件,会抛出FileNotFoundException
方法 描述
void close() 关闭文件,抛出IOException
int read() 从文件中读取一个字符并作为整数返回,如果到达文件末尾则返回 -1,抛出IOException
String readLine() 读取整行并作为字符串返回,如果到达文件末尾则返回null,抛出IOException
void skip(long num) 跳过指定数量的字符
2.1 创建BufferedReader

连接字符流到文件的常见方法是先使用合适的技术创建一个File对象,然后调用FileReader构造函数创建FileReader对象,再将该对象传递给BufferedReader构造函数创建BufferedReader对象。示例代码如下:

File f = new File("movies.txt");
BufferedReader in = new BufferedReader(new FileReader(f));
2.2 从字符流中读取数据

使用BufferedReader类的readLine方法从文件中读取一行数据,当到达文件末尾时,该方法返回null。通常会在while循环中测试readLine方法返回的字符串,以处理文件中的所有行。示例代码如下:

String line = in.readLine();
while (line != null) {
    System.out.println(line);
    line = in.readLine();
}

读取一行数据后,可以使用Java的字符串处理功能提取其中的各个数据。例如,将从 movies.txt 文件中读取的一行数据转换为电影标题(字符串)、年份(整数)和价格(双精度浮点数):

String[] data = line.split("\t");
String title = data[0];
int year = Integer.parseInt(data[1]);
double price = Double.parseDouble(data[2]);

读取完整个文件后,调用close方法关闭流:

in.close();
2.3 完整读取 movies.txt 文件的示例
import java.io.*;
import java.text.NumberFormat;

public class ReadFile {
    public static void main(String[] args) {
        NumberFormat cf = NumberFormat.getCurrencyInstance();
        BufferedReader in = getReader("movies.txt");
        Movie movie = readMovie(in);
        while (movie != null) {
            String msg = Integer.toString(movie.year);
            msg += ": " + movie.title;
            msg += " (" + cf.format(movie.price) + ")";
            System.out.println(msg);
            movie = readMovie(in);
        }
    }

    private static BufferedReader getReader(String name) {
        BufferedReader in = null;
        try {
            File file = new File(name);
            in = new BufferedReader(new FileReader(file));
        } catch (FileNotFoundException e) {
            System.out.println("The file doesn't exist.");
            System.exit(0);
        } catch (IOException e) {
            System.out.println("I/O Error");
            System.exit(0);
        }
        return in;
    }

    private static Movie readMovie(BufferedReader in) {
        String title;
        int year;
        double price;
        String line = "";
        String[] data;
        try {
            line = in.readLine();
        } catch (IOException e) {
            System.out.println("I/O Error");
            System.exit(0);
        }
        if (line == null) {
            return null;
        } else {
            data = line.split("\t");
            title = data[0];
            year = Integer.parseInt(data[1]);
            price = Double.parseDouble(data[2]);
            return new Movie(title, year, price);
        }
    }

    private static class Movie {
        public String title;
        public int year;
        public double price;

        public Movie(String title, int year, double price) {
            this.title = title;
            this.year = year;
            this.price = price;
        }
    }
}

该程序的执行流程如下:
- 程序开始时导入所有Java I/O类。
- 使用 getReader 方法创建一个可以读取文件的BufferedReader对象。
- 使用 readMovie 方法从文件中读取每部电影的信息,返回一个Movie对象,如果到达文件末尾则返回null。
- 使用while循环处理每部电影,构建消息字符串并在控制台显示,然后调用 readMovie 方法读取文件中的下一部电影。
- 程序结束时虽然没有显式关闭文件,但当打开文件的程序结束时,文件会自动关闭。

3. 写入字符流

通常使用PrintWriter类将数据写入文本文件,它就是提供用于写入控制台输出的print和println方法的类。要将打印写入器连接到文本文件,需要使用以下三个类:
- FileWriter :连接到File对象,但只提供基本的写入能力。
- BufferedWriter :连接到FileWriter并提供输出缓冲。如果没有缓冲区,数据会一次写入一个字符到磁盘。该类允许程序在缓冲区中积累数据,只有当缓冲区已满或程序请求写入数据时才会写入。
- PrintWriter :连接到一个Writer对象,可以是BufferedWriter、FileWriter或任何其他扩展抽象Writer类的对象。通常会将该类连接到BufferedWriter。

构造函数 描述
PrintWriter(Writer out) 为指定的输出写入器创建一个打印写入器
PrintWriter(Writer out, boolean flush) 为指定的输出写入器创建一个打印写入器。如果第二个参数为true,每当调用println方法时,缓冲区会自动刷新
BufferedWriter(Writer out) 从指定的写入器创建一个缓冲写入器,通常传递一个FileWriter对象
FileWriter(File file) 从指定的File对象创建一个文件写入器,如果发生错误会抛出IOException
FileWriter(File file, boolean append) 从指定的File对象创建一个文件写入器,如果发生错误会抛出IOException。如果第二个参数为true,若文件已存在,数据会添加到文件末尾
FileWriter(String path) 从指定的路径名创建一个文件写入器,如果发生错误会抛出IOException
FileWriter(String path, boolean append) 从指定的路径名创建一个文件写入器,如果发生错误会抛出IOException。如果第二个参数为true,若文件已存在,数据会添加到文件末尾
方法 描述
void close() 关闭文件
void flush() 将缓冲区的内容写入磁盘
int read() 从文件中读取一个字符并作为整数返回,如果到达文件末尾则返回 -1,抛出IOException
void print(value) 写入值,可以是任何基本类型或任何对象。如果值是对象,会调用对象的toString()方法
void println(value) 写入值,可以是任何基本类型或任何对象。如果值是对象,会调用对象的toString()方法,并在值后面写入一个换行符
3.1 连接PrintWriter到文本文件

要将字符流连接到输出文件,首先创建一个File对象,然后调用PrintWriter构造函数创建一个可以用于写入文件的PrintWriter对象。示例代码如下:

File file = new File("movies.txt");
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));

FileWriter和PrintWriter类都有一个可选的布尔参数,可以为文件流添加额外的功能:
- 如果在FileWriter构造函数中指定true,文件如果存在则会追加数据,即文件中的任何数据都会保留,程序写入的新数据会添加到文件末尾。

File file = new File("movies.txt");
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file, true))); // 追加模式
  • 如果指定false或省略该参数,现有文件会被删除,数据会丢失。
  • PrintWriter类中的布尔参数影响较小,它只是告诉PrintWriter类,每当使用println方法写入一行数据时,应该通知BufferedWriter类刷新其缓冲区。虽然这个选项可能会稍微降低程序的效率,但可以使程序更可靠,因为它减少了在程序或整个计算机崩溃时未写入数据丢失的可能性。
File file = new File("movies.txt");
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)), true); // 刷新模式
3.2 向字符流中写入数据

成功将字符流连接到文件后,向文件写入数据就像向控制台写入文本一样简单,只需使用print和println方法。如果要以分隔格式将数据写入文本文件,需要包含写入分隔符字符的语句。例如:

String title = "Some Movie";
int year = 2023;
System.out.print(title);
System.out.print("\t");
System.out.println(year);

也可以构建一个代表整行的字符串,然后一次性写入:

String line = title + "\t" + year;
System.out.println(line);

如果在创建PrintWriter对象时没有指定刷新选项,仍然可以通过调用flush方法定期强制将缓冲区中的数据写入磁盘:

out.flush();

当完成数据写入后,调用close方法关闭文件:

out.close();
3.3 完整写入 movies.txt 文件的示例
import java.io.*;

public class WriteFile {
    public static void main(String[] args) {
        Movie[] movies = getMovies();
        PrintWriter out = openWriter("movies.txt");
        for (Movie m : movies) {
            writeMovie(m, out);
        }
        out.close();
    }

    private static Movie[] getMovies() {
        Movie[] movies = new Movie[10];
        movies[0] = new Movie("It’s a Wonderful Life", 1946, 14.95);
        movies[1] = new Movie("The Great Race", 1965, 12.95);
        movies[2] = new Movie("Young Frankenstein", 1974, 16.95);
        movies[3] = new Movie("The Return of the Pink Panther", 1975, 11.95);
        movies[4] = new Movie("Star Wars", 1977, 17.95);
        movies[5] = new Movie("The Princess Bride", 1987, 16.95);
        movies[6] = new Movie("Glory", 1989, 14.95);
        movies[7] = new Movie("Apollo 13", 1995, 19.95);
        movies[8] = new Movie("The Game", 1997, 14.95);
        movies[9] = new Movie("The Lord of the Rings: The Fellowship of the Ring", 2001, 19.95);
        return movies;
    }

    private static PrintWriter openWriter(String name) {
        try {
            File file = new File(name);
            PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)), true);
            return out;
        } catch (IOException e) {
            System.out.println("I/O Error");
            System.exit(0);
        }
        return null;
    }

    private static void writeMovie(Movie m, PrintWriter out) {
        String line = m.title;
        line += "\t" + Integer.toString(m.year);
        line += "\t" + Double.toString(m.price);
        out.println(line);
    }

    private static class Movie {
        public String title;
        public int year;
        public double price;

        public Movie(String title, int year, double price) {
            this.title = title;
            this.year = year;
            this.price = price;
        }
    }
}

该程序的执行流程如下:
- 主方法首先调用 getMovies 方法,返回一个要写入文件的Movie对象数组。
- 调用 openWriter 方法创建一个PrintWriter对象,用于向文件写入数据。
- 使用增强for循环为数组中的每部电影调用 writeMovie 方法,该方法接受一个Movie对象和一个PrintWriter对象,创建包含标题、制表符、年份、另一个制表符和价格的字符串,然后将该字符串写入文件。
- 最后关闭PrintWriter对象。

通过以上步骤,我们可以在Java中实现文本文件的读取和写入操作,利用字符流和相应的类,能够高效、灵活地处理文件数据。

Java文件流操作:读写文本文件全解析

4. 操作流程总结与对比

为了更清晰地理解Java中读取和写入字符流的操作,下面通过流程图和对比表格进行总结。

4.1 读取字符流流程图
graph TD;
    A[开始] --> B[创建File对象];
    B --> C[创建FileReader对象];
    C --> D[创建BufferedReader对象];
    D --> E[读取一行数据];
    E --> F{是否到达文件末尾};
    F -- 否 --> G[处理数据];
    G --> E;
    F -- 是 --> H[关闭流];
    H --> I[结束];
4.2 写入字符流流程图
graph TD;
    A[开始] --> B[创建File对象];
    B --> C[创建FileWriter对象];
    C --> D[创建BufferedWriter对象];
    D --> E[创建PrintWriter对象];
    E --> F[准备数据];
    F --> G[写入数据];
    G --> H{是否还有数据};
    H -- 是 --> F;
    H -- 否 --> I[刷新缓冲区];
    I --> J[关闭流];
    J --> K[结束];
4.3 读取与写入操作对比
操作类型 涉及主要类 关键步骤 注意事项
读取字符流 File、FileReader、BufferedReader 1. 创建File对象
2. 创建FileReader对象
3. 创建BufferedReader对象
4. 逐行读取数据并处理
5. 关闭流
处理异常(如文件不存在、I/O错误)
写入字符流 File、FileWriter、BufferedWriter、PrintWriter 1. 创建File对象
2. 创建FileWriter对象
3. 创建BufferedWriter对象
4. 创建PrintWriter对象
5. 准备数据并写入
6. 刷新缓冲区
7. 关闭流
注意文件追加和刷新选项
5. 实际应用场景与优化建议

在实际开发中,Java的文件流操作有很多应用场景,以下是一些常见的场景及相应的优化建议。

5.1 数据备份与恢复

在需要定期备份数据到文本文件,或者从备份文件中恢复数据时,可以使用上述的文件读写操作。例如,一个数据库管理系统可能会在每天凌晨将数据库中的部分数据导出到文本文件进行备份。
- 优化建议
- 对于大量数据的备份,可以采用多线程的方式进行读写操作,提高效率。
- 在写入备份文件时,使用压缩算法(如GZIP)对数据进行压缩,减少文件占用的磁盘空间。

5.2 日志记录

在程序运行过程中,将关键信息记录到文本文件中,方便后续的问题排查和分析。例如,一个Web应用程序可能会记录用户的登录信息、操作记录等。
- 优化建议
- 可以使用日志框架(如Log4j)来管理日志文件的写入,它提供了更灵活的配置选项,如日志级别、日志文件大小限制等。
- 定期清理过期的日志文件,避免占用过多的磁盘空间。

5.3 配置文件读写

程序通常会使用配置文件来存储一些参数和设置,通过文件流操作可以实现对配置文件的读写。例如,一个游戏程序可能会将玩家的游戏设置(如音量、分辨率等)存储在配置文件中。
- 优化建议
- 使用XML或JSON格式的配置文件,这些格式具有更好的可读性和可扩展性。
- 在读取配置文件时,使用缓存机制,避免频繁的文件读写操作。

6. 总结

通过本文的介绍,我们详细了解了Java中使用字符流进行文本文件读写的操作。主要涉及了流的概念、读取和写入字符流所使用的类(如File、FileReader、BufferedReader、FileWriter、BufferedWriter、PrintWriter),以及具体的操作步骤和代码示例。同时,还通过流程图和对比表格对操作流程进行了总结,并且探讨了实际应用场景和优化建议。

在实际开发中,根据不同的需求选择合适的类和方法,合理使用缓冲区和异常处理机制,可以提高程序的性能和可靠性。希望本文能够帮助你更好地掌握Java文件流操作,在实际项目中灵活运用。

总之,Java的文件流操作提供了强大而灵活的功能,无论是处理小型的配置文件,还是大型的数据备份,都能够轻松应对。通过不断的实践和优化,你可以更加熟练地使用这些技术,为你的项目开发带来便利。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值