【问题标题】:JavaFX: How to read and write an ObservableList to a file?JavaFX:如何将 ObservableList 读写到文件中?
【发布时间】:2018-09-01 14:33:51
【问题描述】:

我是编程新手,我想做的是读取/写入存储在ObservableList中的数据,数据来自 Student 类是字符串(名字和姓氏),而且我有一个 TableView 来显示数据。

这是我的代码:

学生班:

import java.io.Serializable;
import javafx.beans.property.SimpleStringProperty;

public class Student implements Serializable {
    private SimpleStringProperty fname;
    private SimpleStringProperty lname;

    Student() {
        this("","");
    }

    Student(String fn, String ln) {
       this.fname = new SimpleStringProperty(fn);
       this.lname = new SimpleStringProperty(ln);
    }


    public void setFirstName(String f) {
        fname.set(f);
    }
    public String getFirstName() {
        return fname.get();
    }

    public void setLastName(String l) {
        lname.set(l);
    }
    public String getLastName() {
        return lname.get();
    }


    @Override
    public String toString() {
        return String.format("%s %s", getFirstName(), getLastName());
    }
}

这是我使用 TextFields 输入数据的代码:

    @FXML
    ObservableList<Student> data = FXCollections.observableArrayList();

    //Just to input the data
    @FXML
    private void handleButtonAction(ActionEvent event) {

        if(!"".equals(txtFirstName.getText()) && !"".equals(txtLastName.getText())){
            data.add(
                    new Student(txtFirstName.getText(),
                                txtLastName.getText()
            ));
        }

        txtFirstName.clear();
        txtLastName.clear();

        // System.out.println(data);
    }

这就是问题所在......

读/写 ObservableList:

    @FXML
    private void HandleMenuSaveAction(ActionEvent event) {
         try {
            FileOutputStream f = new FileOutputStream(new File("saveStudentList.txt"));
            ObjectOutputStream o = new ObjectOutputStream(f);

            o.writeObject(data);
            o.close();
            f.close();

            System.out.println("File Saved Successfully.");

        } catch (FileNotFoundException ex) {
            System.err.println("Save: File not found.");
        } catch (IOException ex) {
            System.err.println("Save: Error initializing stream.");
            ex.printStackTrace();
        } 
    }

    @FXML
    private void HandleMenuLoadAction(ActionEvent event) {
         try {
            FileInputStream fi = new FileInputStream(new File("saveStudentList.txt"));
            ObjectInputStream oi = new ObjectInputStream(fi);

            data = (ObservableList) oi.readObject();

            System.out.println(data.toString());

            //Refresh the Table everytime we load data

            oi.close();
            fi.close();


        } catch (FileNotFoundException ex) {
            System.err.println("Load: File not found.");
        } catch (IOException ex) {
            System.err.println("Load: Error initializing stream.");
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }
    }

这让我java.io.NotSerializableException, 有谁知道如何更改我的代码以使其正常工作?

【问题讨论】:

  • .txt 是存储序列化结果的错误扩展。结果不是文本文件。
  • 您是要将列表序列化为序列化文件还是纯文本文件?
  • @fabian @Zephyr 实际上,我想将列表序列化为序列化文件。我使用.txt 扩展的唯一原因是因为我遵循了this 教程。

标签: javafx io observablelist


【解决方案1】:

Student 对象实现自定义序列化(参见https://stackoverflow.com/a/7290812/2991525)并将ObservableList 的内容复制到ArrayList 以创建可序列化列表:

public class Student implements Serializable {

    private void writeObject(ObjectOutputStream out)
            throws IOException {
        out.writeObject(getFirstName());
        out.writeObject(getLastName());
    }

    private void readObject(ObjectInputStream in)
            throws IOException, ClassNotFoundException {
        fname = new SimpleStringProperty((String) in.readObject());
        lname = new SimpleStringProperty((String) in.readObject());
    }

    ...
}

(反)序列化示例

ObservableList<Student> students = FXCollections.observableArrayList();

for(int i = 0; i < 100; i++) {
    students.add(new Student("Mark"+i, "Miller"+i));
}

ByteArrayOutputStream bos = new ByteArrayOutputStream();

// write list
try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
    oos.writeObject(new ArrayList<>(students));
}

students = null; // make sure the old reference is no longer available

ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());

// read list
try (ObjectInputStream ois = new ObjectInputStream(bis)){
    students = FXCollections.observableList((List<Student>) ois.readObject());
}

System.out.println(students);

【讨论】:

    【解决方案2】:

    当您使用对象序列化时,您的问题有点令人困惑,但您选择的文件名是.txt 文件。序列化对象不像纯文本文件那样可读。

    如果您确实想使用序列化,上述 Fabian 的回答非常棒。但是,如果您希望生成一个简单的文本文件,请查看以下示例程序:

    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import java.io.BufferedReader;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.util.ArrayList;
    import java.util.List;
    
    public class Main {
    
        public static void main(String[] args) {
    
            ObservableList<Student> students = FXCollections.observableArrayList();
    
            // Create some sample students
            students.addAll(
                    new Student("Roger", "Rabbit"),
                    new Student("Elmer", "Fudd"),
                    new Student("Daffy", "Duck"),
                    new Student("Foghorn", "Leghorn"),
                    new Student("Betty", "Boop")
            );
    
            // Write the list to a text file
            try {
                writeToTextFile("students.txt", students);
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            // Now, read the file into a new List<Student>
            List<Student> inputStudents = null;
            try {
                 inputStudents = readStudents("students.txt");
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            // Print out the student names
            if (inputStudents != null) {
                for (Student student : inputStudents) {
                    System.out.println(student.toString());
                }
            }
    
        }
    
        /**
         * Write the list of students to a simple text file with first and last names separated by a comma
         */
        private static void writeToTextFile(String filename, ObservableList<Student> students)
                throws IOException {
    
            FileWriter writer = new FileWriter(filename);
            for (Student student : students) {
                writer.write(student.getFirstName() + "," + student.getLastName() + "\n");
            }
            writer.close();
        }
    
        /**
         * Read the comma-separated list of student names from the text file
         */
        private static List<Student> readStudents(String filename)
                throws IOException {
    
            List<Student> students = new ArrayList<>();
    
            BufferedReader reader = Files.newBufferedReader(Paths.get(filename));
            String line;
            while ((line = reader.readLine()) != null) {
                String[] names = line.split(",");
    
                // Add the student to the list
                students.add(new Student(names[0], names[1]));
    
            }
    
            return students;
        }
    }
    

    这会生成一个非常简单的文本文件,每个学生的名字和姓氏在单独的一行中,用逗号分隔。然后将其读回到一个新列表中,准备在您的TableView 中使用。

    如果您决定走这条路,我建议您找一个好的 CSV 库来处理 CSV(逗号分隔值)文件的读/写。 Apache Commons 有一个不错的可用。

    【讨论】:

      【解决方案3】:

      这是一个示例,它从 ObservableList 的人员(名字和姓氏)读取数据并将数据写入 json 格式的文件。

      列表中保存的 json 数据的示例文件内容

      [ {
        "firstName" : "Fred",
        "lastName" : "Flintstone"
      }, {
        "firstName" : "Wilma",
        "lastName" : "Flintstone"
      }, {
        "firstName" : "Barney",
        "lastName" : "Rubble"
      } ]
      

      实施说明

      数据项存储为人员记录。

      ObservableList 支持 TableView 并保存数据项的记录。

      第 3 方 Jackson 库用于将数据列表序列化和反序列化为 JSON,从文件中存储和读取。

      在启动时,应用程序会生成一个临时文件名,用于在应用程序的生命周期内存储保存的数据文件。

      关机时,临时存档会自动删除。

      module-info 允许 Jackson 数据绑定模块对包含要保存的项目的记录定义的包执行反射。

      在保存和恢复数据项之前,它们会临时存储在 ArrayList 而不是 ObservableList 中。这样做是因为您不想尝试序列化整个 ObservableList。 ObservableList 还将包含可能附加到列表的更改侦听器的条目。您不想序列化这些侦听器。

      使用 Jackson 执行序列化和反序列化的 ListSerializer 类使用 Java 泛型,因此它可以保存和加载可以通过 Jackson 序列化的任何类型的数据(包括示例中的 Person 记录)。泛型在代码中增加了一些复杂性,以确定在序列化和反序列化过程中使用的正确类型。泛型确实允许更通用的解决方案,因此,总的来说,我认为添加通用解决方案值得在实现中增加额外的复杂性。

      ListSerializerController 演示了如何使用 ListSerializer 将数据保存和加载到支持 TableView 的 ObservableList。

      Maven 被用作构建系统。

      JRE 18 和 JavaFX 18 用作运行时。

      示例解决方案

      pom.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
      
          <groupId>com.example</groupId>
          <artifactId>ListSerialization</artifactId>
          <version>1.0-SNAPSHOT</version>
          <name>ListSerialization</name>
      
          <properties>
              <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
              <junit.version>5.8.1</junit.version>
              <javafx.version>18</javafx.version>
          </properties>
      
          <dependencies>
              <dependency>
                  <groupId>org.openjfx</groupId>
                  <artifactId>javafx-controls</artifactId>
                  <version>${javafx.version}</version>
              </dependency>
              <dependency>
                  <groupId>com.fasterxml.jackson.core</groupId>
                  <artifactId>jackson-databind</artifactId>
                  <version>2.13.2.2</version>
              </dependency>
          </dependencies>
      
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-compiler-plugin</artifactId>
                      <version>3.8.1</version>
                      <configuration>
                          <source>18</source>
                          <target>18</target>
                      </configuration>
                  </plugin>
              </plugins>
          </build>
      </project>
      

      src/main/java/module-info.java

      module com.example.listserialization {
          requires javafx.controls;
          requires com.fasterxml.jackson.databind;
      
          opens com.example.listserialization to com.fasterxml.jackson.databind;
          exports com.example.listserialization;
      }
      

      src/main/java/com/example/listserialization/ListSerializerApp.java

      package com.example.listserialization;
      
      import javafx.application.Application;
      import javafx.scene.Scene;
      import javafx.stage.Stage;
      
      import java.io.IOException;
      import java.nio.file.Files;
      import java.nio.file.Path;
      
      public class ListSerializerApp extends Application {
      
          private Path peoplePath;
      
          @Override
          public void init() throws IOException {
              peoplePath = Files.createTempFile(
                      "people",
                      ".json"
              );
              peoplePath.toFile().deleteOnExit();
      
              System.out.println("Using save file name: " + peoplePath);
          }
      
          @Override
          public void start(Stage stage) throws IOException {
              ListSerializerController listSerializerController = new ListSerializerController(
                      peoplePath
              );
      
              stage.setScene(
                      new Scene(
                              listSerializerController.getLayout()
                      )
              );
              stage.show();
          }
      
          public static void main(String[] args) {
              launch();
          }
      }
      

      src/main/java/com/example/listserialization/Person.java

      package com.example.listserialization;
      
      record Person(String firstName, String lastName) {}
      

      src/main/java/com/example/listserialization/ListSerializer.java

      package com.example.listserialization;
      
      import com.fasterxml.jackson.databind.ObjectMapper;
      import com.fasterxml.jackson.databind.SerializationFeature;
      import com.fasterxml.jackson.databind.type.CollectionType;
      
      import java.io.IOException;
      import java.nio.file.Files;
      import java.nio.file.Path;
      import java.util.ArrayList;
      
      public class ListSerializer<T> {
          private static final ObjectMapper mapper =
                  new ObjectMapper()
                          .enable(SerializationFeature.INDENT_OUTPUT);
      
          private final CollectionType listType;
      
          public ListSerializer(Class<T> listItemClass) {
              listType =
                     mapper.getTypeFactory()
                              .constructCollectionType(
                                      ArrayList.class,
                                      listItemClass
                              );
          }
      
          public void serializeList(Path path, ArrayList<T> list) throws IOException {
              Files.writeString(
                      path,
                      mapper.writeValueAsString(
                              list
                      )
              );
          }
      
          public ArrayList<T> deserializeList(Path path) throws IOException {
              return mapper.<ArrayList<T>>readValue(
                      Files.readString(path),
                      listType
              );
          }
      }
      

      src/main/java/com/example/listserialization/ListSerializerController.java

      package com.example.listserialization;
      
      import javafx.beans.property.ReadOnlyStringWrapper;
      import javafx.collections.FXCollections;
      import javafx.event.ActionEvent;
      import javafx.geometry.Insets;
      import javafx.scene.Parent;
      import javafx.scene.control.Button;
      import javafx.scene.control.TableColumn;
      import javafx.scene.control.TableView;
      import javafx.scene.layout.HBox;
      import javafx.scene.layout.VBox;
      
      import java.io.IOException;
      import java.nio.file.Files;
      import java.nio.file.Path;
      import java.util.ArrayList;
      
      public class ListSerializerController {
      
          private final ListSerializer<Person> listSerializer = new ListSerializer<>(
                  Person.class
          );
      
          private final Person[] TEST_PEOPLE = {
                  new Person("Fred", "Flintstone"),
                  new Person("Wilma", "Flintstone"),
                  new Person("Barney", "Rubble")
          };
      
          private final TableView<Person> tableView = new TableView<>(
                  FXCollections.observableArrayList(
                          TEST_PEOPLE
                  )
          );
      
          private final Path peoplePath;
          private final Parent layout;
      
          public ListSerializerController(Path peoplePath) {
              this.peoplePath = peoplePath;
              layout = createLayout();
          }
      
          private Parent createLayout() {
              TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
              firstNameCol.setCellValueFactory(p -> 
                      new ReadOnlyStringWrapper(
                              p.getValue().firstName()
                      ).getReadOnlyProperty()
              );
      
              TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
              lastNameCol.setCellValueFactory(p ->
                      new ReadOnlyStringWrapper(
                              p.getValue().lastName()
                      ).getReadOnlyProperty()
              );
      
              //noinspection unchecked
              tableView.getColumns().setAll(firstNameCol, lastNameCol);
              tableView.setPrefHeight(150);
      
              Button save = new Button("Save");
              save.setOnAction(this::save);
      
              Button clear = new Button("Clear");
              clear.setOnAction(this::clear);
      
              Button load = new Button("Load");
              load.setOnAction(this::load);
      
              Button restoreDefault = new Button("Default Data");
              restoreDefault.setOnAction(this::restoreDefault);
      
              HBox controls = new HBox(10, save, clear, load, restoreDefault);
      
              VBox layout = new VBox(10, controls, tableView);
              layout.setPadding(new Insets(10));
      
              return layout;
          }
      
          public Parent getLayout() {
              return layout;
          }
      
          private void save(ActionEvent e) {
              try {
                  listSerializer.serializeList(
                          peoplePath,
                          new ArrayList<>(
                                  tableView.getItems()
                          )
                  );
      
                  System.out.println("Saved to: " + peoplePath);
                  System.out.println(Files.readString(peoplePath));
              } catch (IOException ex) {
                  ex.printStackTrace();
              }
          }
      
          private void clear(ActionEvent e) {
              tableView.getItems().clear();
              System.out.println("Cleared data in UI");
          }
      
          private void load(ActionEvent e) {
              try {
                  if (!peoplePath.toFile().exists()) {
                      tableView.getItems().clear();
                      System.out.println("Saved data file does not exist, clearing data: " + peoplePath);
      
                      return;
                  }
      
                  tableView.getItems().setAll(
                          listSerializer.deserializeList(peoplePath)
                  );
      
                  System.out.println("Loaded data from: " + peoplePath);
                  System.out.println(Files.readString(peoplePath));
              } catch (IOException ex) {
                  ex.printStackTrace();
              }
          }
      
          private void restoreDefault(ActionEvent e) {
              tableView.getItems().setAll(TEST_PEOPLE);
              System.out.println("Restored default data in UI");
          }
      
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-04-24
        • 1970-01-01
        • 2019-01-18
        • 2017-04-30
        • 2014-04-07
        • 1970-01-01
        • 1970-01-01
        • 2015-12-27
        相关资源
        最近更新 更多