掌握Java文件操作:实现GUI程序的数据持久化

112 阅读25分钟
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8

前言

  在上一期的课程中,我们探讨了Java GUI编程中的多线程与并发处理。通过学习 Thread 类、SwingWorker 类以及 ExecutorService 接口,我们掌握了如何在GUI应用程序中处理耗时任务,并通过多线程技术提升程序的响应性和用户体验。多线程技术的应用使得程序在处理复杂操作时,能够保持界面的流畅性和操作的及时响应。然而,在实际开发中,仅仅依靠内存来存储和管理数据是不够的。为了让程序的数据能够在关闭后仍然保留,并在下次启动时重新加载,我们需要引入文件I/O与数据持久化的概念。

摘要

  本文将详细讨论Java GUI编程中的文件I/O与数据持久化技术。我们将从基础的文件读写操作入手,逐步介绍如何通过流处理实现数据的持久化存储,以及在GUI应用程序中如何管理数据的保存与加载。通过核心源码解读和实际案例分析,本文将帮助读者理解如何在Java中有效地实现数据持久化,并在实际项目中灵活运用这些技术。文章还将讨论文件I/O与数据持久化的优缺点,并提供相关的测试用例以验证代码的正确性。

简介

  文件I/O(输入/输出)和数据持久化是Java编程中不可或缺的组成部分。文件I/O操作允许程序与外部文件进行交互,如读取配置文件、保存用户数据等;而数据持久化则确保程序的数据在程序关闭后仍能保留,并在重新启动时恢复。这些功能对于开发实际应用程序至关重要,特别是在需要保存用户设置、处理日志文件或管理大规模数据时。

  Java提供了丰富的API来处理文件I/O操作,包括 File 类、InputStreamOutputStream 类、ReaderWriter 类等。同时,通过序列化(Serialization)机制,Java还能够将对象的状态保存到文件中,并在需要时重新加载,从而实现复杂数据结构的持久化存储。

概述

文件I/O的基本概念

  在Java中,文件I/O指的是通过程序与文件系统进行交互,包括文件的创建、读取、写入和删除等操作。Java为此提供了多种类和接口:

  • File 类:用于表示文件或目录路径,提供文件的基本操作,如创建、删除、重命名等。
  • InputStream/OutputStream:字节流的父类,主要用于处理二进制数据。
  • Reader/Writer:字符流的父类,主要用于处理文本数据。
  • BufferedReader/BufferedWriter:带有缓冲功能的字符流,用于提高读写效率。

数据持久化

  数据持久化是指将程序中的数据保存到存储介质(如文件、数据库)中,使其在程序关闭后仍能保留,并在下次启动时恢复。Java中常用的数据持久化方法包括:

  • 对象序列化:将对象的状态保存到文件中,并在需要时恢复。通过 ObjectOutputStreamObjectInputStream 类可以实现对象的序列化与反序列化。
  • 文件存储:将数据以文本或二进制形式存储在文件中,常用于保存配置、日志等。

核心源码解读

  我们通过一个简单的案例,展示如何使用Java的文件I/O API实现数据的读写操作,并通过对象序列化实现数据持久化。

文件读写操作示例

代码示例

import java.io.*;

/**
 * @Author bug菌
 * @Source 公众号:猿圈奇妙屋
 * @Date 2024-10-05 21:51
 */
public class FileIOExample {
    public static void main(String[] args) {
        String filePath = "example.txt";

        // 写入数据到文件
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
            writer.write("Hello, Java File I/O!");
            writer.newLine();
            writer.write("This is a simple file I/O example.");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 从文件读取数据
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果

  根据如上的测试用例,作者在本地进行测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加其他的测试数据或测试方法,以便于进行熟练学习以此加深知识点的理解。

代码解析

  接着我将对上述代码逐句进行一个详细解读,希望能够帮助到同学们,能以最快的速度对其知识点掌握于心,这也是我写此文的初衷,授人以鱼不如授人以渔,只有将其原理摸透,日后应对场景使用,才能得心应手,如鱼得水。所以如果有基础的同学,可以略过如下代码解析,针对没基础的同学,还是需要加强对代码的逻辑与实现,方便日后的你能更深入理解它并常规使用不受限制。

  这段代码展示了如何使用 Java 中的文件输入输出(I/O)操作。它使用 BufferedWriterBufferedReader 来分别进行文件的写入和读取操作。下面是代码的详细解析:

1. 文件路径定义
String filePath = "example.txt";
  • 这里定义了文件路径 filePath,文件名为 "example.txt"。文件会保存在程序的运行目录下,如果该文件不存在,程序会自动创建它。
2. 文件写入操作
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
    writer.write("Hello, Java File I/O!");
    writer.newLine();
    writer.write("This is a simple file I/O example.");
} catch (IOException e) {
    e.printStackTrace();
}
解析:
  • 这里使用了 BufferedWriterFileWriter 来进行文件写入操作。BufferedWriter 为文件输出提供了缓冲,提高了写入的效率。
  • FileWriter(filePath) 用于指定要写入的文件路径。如果文件不存在,它会自动创建该文件。
  • writer.write("...") 将指定的内容写入到文件。
  • writer.newLine() 用于在文件中插入一个换行符。
  • 该块代码使用了 try-with-resources 语法,这是一种自动关闭资源的机制。即在 try 代码块执行完毕后,BufferedWriter 会自动关闭,释放资源,无需显式调用 close()
  • 如果发生 IOException,会捕获并打印异常信息。
3. 文件读取操作
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}
解析:
  • 这里使用了 BufferedReaderFileReader 来从文件中读取数据。BufferedReader 也提供了缓冲机制,提升了读取效率。
  • FileReader(filePath) 用于读取指定路径下的文件。
  • reader.readLine() 会逐行读取文件的内容,每次读取一行。如果到达文件末尾,会返回 null
  • 读取的每一行内容都通过 System.out.println(line) 输出到控制台。
  • 同样,使用了 try-with-resources 语法,BufferedReader 会在读取操作结束后自动关闭。
4. 程序运行结果

当程序运行时,它会首先在 example.txt 文件中写入以下两行内容:

Hello, Java File I/O!
This is a simple file I/O example.

接着,程序会从文件中读取内容,并输出到控制台。输出结果如下:

Hello, Java File I/O!
This is a simple file I/O example.
5. 异常处理

在整个文件操作过程中,程序对 IOException 进行了捕获。这种异常可能在文件不存在、权限不足或磁盘空间不足等情况下发生。通过 e.printStackTrace(),可以将异常的详细信息输出到控制台,便于调试和问题排查。

对象序列化与反序列化示例

import java.io.*;
/**
 * @Author bug菌
 * @Source 公众号:猿圈奇妙屋
 * @Date 2024-10-05 21:57
 */
public class SerializationExample {
    public static void main(String[] args) {
        String filePath = "person.ser";

        // 序列化对象到文件
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath))) {
            Person person = new Person("Alice", 30);
            oos.writeObject(person);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 从文件反序列化对象
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath))) {
            Person person = (Person) ois.readObject();
            System.out.println("Deserialized Person: " + person);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

运行结果

  根据如上的测试用例,作者在本地进行测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加其他的测试数据或测试方法,以便于进行熟练学习以此加深知识点的理解。

代码解析

  接着我将对上述代码逐句进行一个详细解读,希望能够帮助到同学们,能以最快的速度对其知识点掌握于心,这也是我写此文的初衷,授人以鱼不如授人以渔,只有将其原理摸透,日后应对场景使用,才能得心应手,如鱼得水。所以如果有基础的同学,可以略过如下代码解析,针对没基础的同学,还是需要加强对代码的逻辑与实现,方便日后的你能更深入理解它并常规使用不受限制。

  这段代码展示了 Java 中序列化和反序列化的示例。它通过 ObjectOutputStreamObjectInputStream 将对象序列化为文件,并从文件反序列化为对象。具体分析如下:

1. 序列化与反序列化
  • 序列化:是指将 Java 对象转换为字节流,以便将其存储到文件、数据库或通过网络传输。
  • 反序列化:是将字节流恢复为原始 Java 对象的过程。
2. 文件路径定义
String filePath = "person.ser";
  • filePath 定义了保存序列化对象的文件路径,文件名为 person.ser。扩展名 .ser 通常用于表示序列化的文件。
3. 序列化对象到文件
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath))) {
    Person person = new Person("Alice", 30);
    oos.writeObject(person);
} catch (IOException e) {
    e.printStackTrace();
}
解析:
  • ObjectOutputStream 用于将对象序列化为字节流,并通过 FileOutputStream 写入文件。
  • Person person = new Person("Alice", 30); 创建了一个 Person 对象,拥有 name"Alice"age30 的属性。
  • oos.writeObject(person);person 对象写入到文件中。
  • try-with-resources 确保 ObjectOutputStream 在使用完毕后自动关闭,避免资源泄漏。
4. 反序列化对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath))) {
    Person person = (Person) ois.readObject();
    System.out.println("Deserialized Person: " + person);
} catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
}
解析:
  • ObjectInputStream 用于从字节流中读取对象,并通过 FileInputStream 从文件中加载序列化的数据。
  • Person person = (Person) ois.readObject(); 从文件中读取序列化的 Person 对象,并进行类型转换为 Person
  • 输出反序列化的对象信息:Deserialized Person: Alice, 30
  • 如果文件不存在、数据损坏或对象类找不到(如代码更新),可能会抛出 IOExceptionClassNotFoundException
5. Person

要让对象能够被序列化,Person 类需要实现 Serializable 接口:

import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}
解析:
  • Person 类实现了 Serializable 接口,这个接口是一个标记接口,不需要实现任何方法,只是为了告诉 Java 序列化机制该类可以被序列化。
  • Person 类有两个属性:nameage
  • toString() 方法用于在打印对象时,提供一个清晰的输出格式,便于展示对象内容。
6. 程序运行流程
  • 序列化过程
    1. 创建一个 Person 对象。
    2. 使用 ObjectOutputStream 将该对象序列化,并将字节流写入到 person.ser 文件中。
  • 反序列化过程
    1. 使用 ObjectInputStream 读取 person.ser 文件中的字节流。
    2. 将字节流恢复为 Person 对象。
    3. 输出反序列化后的 Person 对象内容。
7. 运行结果

程序的输出结果可能如下:

Deserialized Person: Person{name='Alice', age=30}
8. 总结
  • 这段代码展示了 Java 中对象的序列化和反序列化过程,使用 ObjectOutputStreamObjectInputStream 进行文件 I/O 操作。
  • 序列化的作用是将对象持久化或通过网络传输,而反序列化则是将数据恢复为对象。
  • 注意:要序列化的对象类必须实现 Serializable 接口,且类中的所有字段也必须是可序列化的。如果某个字段不想被序列化,可以使用 transient 关键字修饰。

案例分析

案例:实现一个简单的用户设置管理器

代码示例

  下面我们通过一个案例展示如何使用文件I/O和数据持久化技术来实现一个简单的用户设置管理器。该案例将演示如何保存和加载用户设置,如窗口大小、主题颜色等。

import java.io.*;
import javax.swing.*;
import java.awt.*;

class UserSettings implements Serializable {
    private static final long serialVersionUID = 1L;
    private int windowWidth;
    private int windowHeight;
    private Color themeColor;

    public UserSettings(int windowWidth, int windowHeight, Color themeColor) {
        this.windowWidth = windowWidth;
        this.windowHeight = windowHeight;
        this.themeColor = themeColor;
    }

    public int getWindowWidth() {
        return windowWidth;
    }

    public int getWindowHeight() {
        return windowHeight;
    }

    public Color getThemeColor() {
        return themeColor;
    }
}

public class UserSettingsManager {
    private static final String SETTINGS_FILE = "userSettings.ser";

    public static void saveSettings(UserSettings settings) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(SETTINGS_FILE))) {
            oos.writeObject(settings);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static UserSettings loadSettings() {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(SETTINGS_FILE))) {
            return (UserSettings) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return new UserSettings(800, 600, Color.LIGHT_GRAY); // 默认设置
        }
    }

    public static void main(String[] args) {
        UserSettings settings = loadSettings();

        JFrame frame = new JFrame("User Settings Manager");
        frame.setSize(settings.getWindowWidth(), settings.getWindowHeight());
        frame.getContentPane().setBackground(settings.getThemeColor());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);

        // 模拟用户更改设置并保存
        settings = new UserSettings(1024, 768, Color.DARK_GRAY);
        saveSettings(settings);
    }
}

运行结果

  根据如上的测试用例,作者在本地进行测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加其他的测试数据或测试方法,以便于进行熟练学习以此加深知识点的理解。

代码解析

  接着我将对上述代码逐句进行一个详细解读,希望能够帮助到同学们,能以最快的速度对其知识点掌握于心,这也是我写此文的初衷,授人以鱼不如授人以渔,只有将其原理摸透,日后应对场景使用,才能得心应手,如鱼得水。所以如果有基础的同学,可以略过如下代码解析,针对没基础的同学,还是需要加强对代码的逻辑与实现,方便日后的你能更深入理解它并常规使用不受限制。

  这段代码展示了如何通过序列化和反序列化机制保存和加载用户的窗口设置。主要功能是管理用户的界面设置(如窗口大小、主题颜色)并将这些设置保存在一个序列化文件中,从而实现持久化。下面是代码的详细解析:

1. 定义常量 SETTINGS_FILE
private static final String SETTINGS_FILE = "userSettings.ser";
  • 这是保存用户设置的序列化文件路径。文件名为 userSettings.ser,扩展名 .ser 代表这是一个序列化文件,用于存储对象数据。
2. saveSettings 方法
public static void saveSettings(UserSettings settings) {
    try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(SETTINGS_FILE))) {
        oos.writeObject(settings);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
  • 该方法用于将 UserSettings 对象序列化并保存到文件中。
  • ObjectOutputStreamsettings 对象序列化为字节流,使用 FileOutputStream 将字节流写入到 userSettings.ser 文件。
  • 采用了 try-with-resources 语法,确保流在使用完后自动关闭,避免资源泄漏。
3. loadSettings 方法
public static UserSettings loadSettings() {
    try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(SETTINGS_FILE))) {
        return (UserSettings) ois.readObject();
    } catch (IOException | ClassNotFoundException e) {
        e.printStackTrace();
        return new UserSettings(800, 600, Color.LIGHT_GRAY); // 默认设置
    }
}
  • 该方法用于从文件中加载并反序列化 UserSettings 对象。
  • 使用 ObjectInputStreamuserSettings.ser 文件中读取序列化的数据,并将其反序列化为 UserSettings 对象。
  • 如果文件不存在或者发生错误(如类找不到、文件读取失败),则会返回一个默认的 UserSettings 对象,默认的窗口大小为 800x600,颜色为 Color.LIGHT_GRAY
4. main 方法
public static void main(String[] args) {
    UserSettings settings = loadSettings();

    JFrame frame = new JFrame("User Settings Manager");
    frame.setSize(settings.getWindowWidth(), settings.getWindowHeight());
    frame.getContentPane().setBackground(settings.getThemeColor());
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);

    // 模拟用户更改设置并保存
    settings = new UserSettings(1024, 768, Color.DARK_GRAY);
    saveSettings(settings);
}
  • 在程序启动时,首先调用 loadSettings() 加载之前保存的用户设置。如果加载失败,会使用默认设置。
  • 创建一个 JFrame 窗口,窗口的大小和背景颜色根据加载的设置进行配置:
    • 窗口大小通过 settings.getWindowWidth()settings.getWindowHeight() 来设置。
    • 窗口背景颜色通过 settings.getThemeColor() 来设置。
  • frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) 用于确保用户关闭窗口时程序正常退出。
  • 在模拟用户更改设置的情况下,新的设置(窗口大小 1024x768,颜色 Color.DARK_GRAY)会被保存到文件中。
5. UserSettings

  为了保存用户的窗口设置,我们需要一个 UserSettings 类,该类应该实现 Serializable 接口,以便支持序列化和反序列化操作。假设该类如下所示:

import java.awt.*;
import java.io.Serializable;

public class UserSettings implements Serializable {
    private int windowWidth;
    private int windowHeight;
    private Color themeColor;

    public UserSettings(int windowWidth, int windowHeight, Color themeColor) {
        this.windowWidth = windowWidth;
        this.windowHeight = windowHeight;
        this.themeColor = themeColor;
    }

    public int getWindowWidth() {
        return windowWidth;
    }

    public int getWindowHeight() {
        return windowHeight;
    }

    public Color getThemeColor() {
        return themeColor;
    }
}
解析:
  • UserSettings 实现了 Serializable 接口,使其可以被序列化和反序列化。
  • 包含了三个属性:
    • windowWidth:窗口的宽度。
    • windowHeight:窗口的高度。
    • themeColor:窗口的主题颜色。
  • 提供了构造方法用于初始化这些属性,提供了相应的 getter 方法用于获取属性值。
6. 程序运行流程
  1. 程序启动时,会先尝试从文件 userSettings.ser 中加载用户的设置(窗口大小和颜色)。
  2. 如果文件存在且数据有效,窗口会使用加载的设置来显示。如果文件不存在或读取失败,则使用默认设置。
  3. 窗口显示后,模拟用户修改了设置,将新的设置(窗口大小 1024x768,颜色 Color.DARK_GRAY)保存到文件中。
  4. 下次程序启动时,窗口会使用用户保存的设置进行显示。
7. 小结
  • 这段代码展示了如何通过序列化机制保存和加载用户的设置,以实现设置的持久化。
  • 使用 ObjectOutputStreamObjectInputStream 可以轻松将对象保存到文件中,或从文件中恢复对象。
  • 通过 JFrame,程序可以基于用户的设置显示不同的窗口尺寸和主题颜色,提供良好的用户体验。

应用场景演示

  文件I/O与数据持久化在实际应用中有广泛的应用场景,以下是一些常见的例子:

  1. 配置文件管理:保存和加载应用程序的配置,如窗口位置、用户首选项等。
  2. 日志记录:将应用程序的运行日志记录到文件中,便于后期分析和调试。
  3. 数据存储:保存用户数据、应用程序状态等,使其在程序重启后能够恢复。
  4. 备份与恢复:定期将重要数据备份到文件中,并在需要时从备份中恢复数据。

优缺点分析

优点

  • 数据持久化:通过文件I/O和序列化,应用程序的数据可以跨越会话保存,用户不必每次启动程序都重新配置设置。
  • 灵活性:Java的文件I/O API提供了丰富的操作方式,可以处理各种类型的文件和数据格式。
  • 易于实现:基本的文件读写和对象序列化操作相对简单,可以快速实现基本的数据持久化需求。

缺点

  • 性能限制:对于大规模数据,文件I/O操作的性能可能会成为瓶颈,尤其是在处理大量数据或高频率的读写操作时。
  • 数据一致性问题:在多线程环境中,如果多个线程同时访问和修改同一个文件,可能会导致数据不一致问题。
  • 安全性:文件存储的数据容易受到恶意攻击或误操作的影响,特别是敏感数据的存储需要额外的加密保护。

类代码方法介绍及演示

  以下展示如何使用 Properties 类来管理应用程序的配置文件,以实现简单的配置持久化。

import java.io.*;
import java.util.Properties;

public class ConfigManager {
    private static final String CONFIG_FILE = "config.properties";
    private Properties properties;

    public ConfigManager() {
        properties = new Properties();
        loadProperties();
    }

    private void loadProperties() {
        try (InputStream input = new FileInputStream(CONFIG_FILE)) {
            properties.load(input);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void saveProperties() {
        try (OutputStream output = new FileOutputStream(CONFIG_FILE)) {
            properties.store(output, null);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String getProperty(String key, String defaultValue) {
        return properties.getProperty(key, defaultValue);
    }

    public void setProperty(String key, String value) {
        properties.setProperty(key, value);
    }

    public static void main(String[] args) {
        ConfigManager configManager = new ConfigManager();
        System.out.println("Initial value: " + configManager.getProperty("username", "defaultUser"));

        // 修改配置并保存
        configManager.setProperty("username", "newUser");
        configManager.saveProperties();

        // 重新加载配置
        configManager = new ConfigManager();
        System.out.println("Updated value: " + configManager.getProperty("username", "defaultUser"));
    }
}

代码解析

  接着我将对上述代码逐句进行一个详细解读,希望能够帮助到同学们,能以最快的速度对其知识点掌握于心,这也是我写此文的初衷,授人以鱼不如授人以渔,只有将其原理摸透,日后应对场景使用,才能得心应手,如鱼得水。所以如果有基础的同学,可以略过如下代码解析,针对没基础的同学,还是需要加强对代码的逻辑与实现,方便日后的你能更深入理解它并常规使用不受限制。

  这段代码展示了如何使用 Java 的 Properties 类来读取和写入配置文件(.properties 文件)。Properties 类是 Java 提供的一个用于处理键值对配置的实用工具类,特别适合用来管理应用程序的配置信息,如用户名、密码等。下面是对代码的详细解析:

1. CONFIG_FILEproperties
private static final String CONFIG_FILE = "config.properties";
private Properties properties;
  • CONFIG_FILE 是配置文件的文件名,这里是 config.properties,用来存储应用的配置数据。
  • propertiesProperties 类的一个实例,用于加载、保存和管理配置项。
2. 构造方法 ConfigManager()
public ConfigManager() {
    properties = new Properties();
    loadProperties();
}
  • 构造方法初始化 properties 实例,并调用 loadProperties() 方法从文件中加载配置数据。
3. loadProperties() 方法
private void loadProperties() {
    try (InputStream input = new FileInputStream(CONFIG_FILE)) {
        properties.load(input);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
解析:
  • 该方法从 config.properties 文件中加载配置数据。
  • 使用 InputStream 读取文件内容,并通过 properties.load() 方法将配置加载到 properties 对象中。
  • try-with-resources 语法保证了 InputStream 在使用完毕后会自动关闭,避免资源泄漏。
  • 如果文件不存在或读取时发生错误,异常会被捕获并打印堆栈跟踪。
4. saveProperties() 方法
public void saveProperties() {
    try (OutputStream output = new FileOutputStream(CONFIG_FILE)) {
        properties.store(output, null);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
解析:
  • 该方法将当前的配置保存到 config.properties 文件中。
  • 使用 OutputStream 将数据写入文件,并通过 properties.store() 方法将 properties 对象中的数据保存到文件中。
  • properties.store() 的第二个参数是注释,传入 null 表示不添加注释。
  • 和读取配置一样,使用了 try-with-resources 语法以确保流在使用完毕后正确关闭。
5. getProperty()setProperty() 方法
public String getProperty(String key, String defaultValue) {
    return properties.getProperty(key, defaultValue);
}

public void setProperty(String key, String value) {
    properties.setProperty(key, value);
}
解析:
  • getProperty():获取指定键的值,如果配置文件中没有该键,则返回默认值 defaultValue
  • setProperty():设置键值对,将配置项添加到 properties 对象中。
6. main() 方法
public static void main(String[] args) {
    ConfigManager configManager = new ConfigManager();
    System.out.println("Initial value: " + configManager.getProperty("username", "defaultUser"));

    // 修改配置并保存
    configManager.setProperty("username", "newUser");
    configManager.saveProperties();

    // 重新加载配置
    configManager = new ConfigManager();
    System.out.println("Updated value: " + configManager.getProperty("username", "defaultUser"));
}
解析:
  • 程序启动时,首先创建一个 ConfigManager 实例,并调用 getProperty("username", "defaultUser") 从配置文件中读取 username 的值。如果 username 键不存在,则返回默认值 "defaultUser"
  • 接下来,通过 setProperty("username", "newUser") 修改配置,随后调用 saveProperties() 将新的配置保存到 config.properties 文件中。
  • 再次创建一个新的 ConfigManager 实例,调用 loadProperties() 从文件中重新加载配置,并输出更新后的 username 值。
7. 运行效果

如果 config.properties 文件不存在,程序第一次运行时将输出:

Initial value: defaultUser

然后,它会将 username 设置为 "newUser" 并保存到文件中。

再次运行程序时,将输出:

Updated value: newUser
8. config.properties 文件内容

在修改并保存 username"newUser" 之后,config.properties 文件将包含类似以下的内容:

username=newUser
总结
  • 这段代码演示了如何使用 Properties 类读取、修改、保存应用程序的配置文件。
  • Properties 类可以简化配置管理,特别适用于存储简单的键值对。
  • 通过使用 try-with-resources 确保资源(如文件流)的自动关闭,避免资源泄漏问题。

测试用例

  为了验证文件I/O与数据持久化功能的正确性,我们可以编写以下测试用例。

测试代码

/**
 * @Author bug菌
 * @Source 公众号:猿圈奇妙屋
 * @Date 2024-10-05 22:06
 */
public class ConfigManagerTest {
    public static void main(String[] args) {
        ConfigManager configManager = new ConfigManager();

        // 初始测试
        String initialUsername = configManager.getProperty("username", "defaultUser");
        System.out.println("Initial Username: " + initialUsername);
        assert initialUsername.equals("defaultUser");

        // 修改配置并保存
        configManager.setProperty("username", "testUser");
        configManager.saveProperties();

        // 重新加载测试
        configManager = new ConfigManager();
        String updatedUsername = configManager.getProperty("username", "defaultUser");
        System.out.println("Updated Username: " + updatedUsername);
        assert updatedUsername.equals("testUser");
    }
}

测试结果预期

  在运行测试代码时,应首先输出“Initial Username: defaultUser”,表示加载默认配置成功。修改配置并保存后,再次加载配置应输出“Updated Username: testUser”,表示配置的修改和持久化成功。

测试代码分析

  在本次的测试用例分析中,我将带领同学们深入探讨测试代码的每一个环节,确保每位同学都能够对测试过程有一个全面而深刻的理解。通过这种细致的讲解,我希望能够加强同学们对测试重要性的认识,并帮助大家更好地掌握测试技巧,最重要的是掌握本期的核心知识点,早日把它学会并运用到日常开发中去。

这段代码是 ConfigManager 类的测试用例,通过一个简单的 main 方法来验证配置文件的读取、修改和保存功能是否正常工作。下面是对代码的详细解析:

1. 初始化 ConfigManager

ConfigManager configManager = new ConfigManager();
  • 创建一个 ConfigManager 实例,它会自动调用构造方法,加载 config.properties 文件中的配置。如果文件不存在,程序会捕获异常并继续执行。

2. 初始测试

String initialUsername = configManager.getProperty("username", "defaultUser");
System.out.println("Initial Username: " + initialUsername);
assert initialUsername.equals("defaultUser");
解析:
  • 通过 configManager.getProperty("username", "defaultUser") 获取配置文件中的 username 值。如果文件中没有定义 username,会返回默认值 "defaultUser"
  • System.out.println("Initial Username: " + initialUsername) 输出初始的用户名。
  • 使用 assert 断言检查 initialUsername 是否等于 "defaultUser"。如果条件不成立,程序将抛出 AssertionError。这可以帮助开发者在测试过程中及时发现错误。

3. 修改配置并保存

configManager.setProperty("username", "testUser");
configManager.saveProperties();
解析:
  • 使用 setProperty("username", "testUser")username 的值设置为 "testUser"
  • 调用 saveProperties() 方法将更改后的配置保存到 config.properties 文件中。

4. 重新加载测试

configManager = new ConfigManager();
String updatedUsername = configManager.getProperty("username", "defaultUser");
System.out.println("Updated Username: " + updatedUsername);
assert updatedUsername.equals("testUser");
解析:
  • 重新创建一个新的 ConfigManager 实例,这会触发再次加载配置文件。
  • 调用 getProperty("username", "defaultUser") 方法来获取重新加载后的 username 值。
  • 打印输出更新后的用户名。
  • 使用 assert 检查更新后的 username 是否为 "testUser",如果断言失败则抛出异常。

5. 测试输出

初次运行程序时:
  • 如果 config.properties 文件不存在,初始时会返回默认值 "defaultUser"
  • 程序运行后,保存的新用户名是 "testUser",并将其保存到文件中。

输出可能如下:

Initial Username: defaultUser
Updated Username: testUser
再次运行程序时:
  • 如果 config.properties 文件已经存在,且 username"testUser",程序会加载这个值,并显示:
Initial Username: testUser
Updated Username: testUser

6. 总结

  • 这段测试代码验证了 ConfigManager 的读取、修改和保存功能。
  • 使用 assert 语句来确保程序逻辑正确,能够在测试过程中发现潜在的错误。
  • ConfigManagerTest 模拟了真实的配置加载和保存流程,通过修改配置文件验证持久化是否正确实现。

实际运行结果

  根据如上的测试用例,作者在本地进行测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加其他的测试数据或测试方法,以便于进行熟练学习以此加深知识点的理解。

小结

  本文通过对Java文件I/O与数据持久化技术的详细解析,帮助读者理解了如何在GUI应用程序中有效地实现数据的保存与恢复。通过对文件读写操作、对象序列化以及配置文件管理的深入讲解,读者能够在实际项目中灵活应用这些技术,确保程序的数据在多次会话之间保持一致。

总结

  文件I/O与数据持久化是Java编程中的基础技术,尤其在GUI编程中,通过这些技术,程序能够为用户提供更好的数据管理体验。掌握文件读写、对象序列化以及配置管理等知识,开发者可以创建出更加健壮和用户友好的应用程序。希望通过本文的学习,你能够在自己的项目中自信地应用这些知识,并进一步提升你的Java开发技能。

下期预告:JavaFX简介与迁移

  在下一期的文章中,我们将探讨JavaFX简介与迁移。JavaFX是Java用于构建现代化桌面应用程序的强大工具,相较于Swing,JavaFX具有更好的UI组件支持和性能优化。我们将介绍JavaFX的基本概念、常用组件以及从Swing迁移到JavaFX的实践方法,敬请期待!

  ok,以上就是我这期的全部内容啦,如果还想学习更多,你可以看看如下的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能实现财富自由的佬儿。「赠人玫瑰,手留余香」,咱们下期拜拜~~

附录源码

  如上涉及所有源码均已上传同步在「Gitee」,提供给同学们一对一参考学习,辅助你更迅速的掌握。