序列化 - 为什么 readObject 不读取整个文件?

Serialization - Why readObject doesn't reads whole file?

我是 Java 的初学者,我遇到 read/write 到文本文件的问题。

过程:

这里是 main.java :

import fr.ocr.vehicule2.*;

public class Main {
    public static void main(String[] args) {
     Garage garage = new Garage();
     System.out.println(garage);

     Vehicule lag1 = new Lagouna();
     lag1.setMoteur(new MoteurEssence("150 Chevaux", 10256d));
     lag1.addOption(new GPS());
     lag1.addOption(new SiegeChauffant());
     lag1.addOption(new VitreElectrique());
     garage.addVoiture(lag1);

     Vehicule A300B_2 = new A300B();
     A300B_2.setMoteur(new MoteurElectrique("1500 W", 1234d));
     A300B_2.addOption(new Climatisation());
     A300B_2.addOption(new BarreDeToit());
     A300B_2.addOption(new SiegeChauffant());
     garage.addVoiture(A300B_2);
}

这里是garage.java

package fr.ocr.vehicule2;

import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;


public class Garage {

  private List<Vehicule> voitures = new ArrayList<Vehicule>();;
  ObjectInputStream ois;
  ObjectOutputStream oos;

  public Garage(){
  }

  public String toString() {
    System.out.println("DEBUG start toString");

    String str = "";
    // Vérification de l'existence du fichier de sauvegarde
    if(Files.notExists(Paths.get("garage.txt"))) str += "Aucune voiture sauvegardée !\n";

    str += "*************************\n" 
       + "* Garage OpenClassrooms *\n"
       + "*************************\n";

    // Lecture du fichier texte
    if(Files.exists(Paths.get("garage.txt"))) {
      try {
        ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(new File("garage.txt"))));
          str += ((Vehicule)ois.readObject()).toString();
        ois.close();
      } catch (FileNotFoundException e) {
        e.printStackTrace();
      } catch (ClassNotFoundException e) {
        e.printStackTrace();
      } catch (EOFException e) {
        e.printStackTrace();
      } catch (IOException e) {
        e.printStackTrace();
      } 
    }
    System.out.println("DEBUG end toString");
    return str;
  }

  public void addVoiture(Vehicule voit) {

    System.out.println("DEBUG start addVoiture" + voit);

    voitures.add(voit);

    // écriture du fichier texte
    try {
      oos = new ObjectOutputStream(
          new BufferedOutputStream(
            new FileOutputStream(
              new File("garage.txt"))));

//      for(Vehicule V:voitures){
//              oos.writeObject(V);
//          }
      oos.writeObject(voit);
      oos.close();

    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } 
    System.out.println("DEBUG end addVoiture");
  }
}

第一次执行 main.java,(文本文件不存在)我进入控制台:

这就是我想在首次启动时在控制台中显示的内容。

但是第二次执行 main.java,(文本文件存在),我进入控制台:

我想要的结果是他们两个,按照我添加它们的方式排序:

我找不到这个问题的解决方案, 是因为我没有正确写两辆车:oos.writeObject(voit); 或者因为我没有正确阅读它:str += ((Vehicule)ois.readObject()).toString(); ?

我什至在 Whosebug 上搜索了很多,但代码似乎是正确的。所以这可能是我写入然后读取文件的方式有问题?

如果有人能提供帮助,非常感谢,我花了很多时间但转过身来。


在@Kevin Anderson 信息之后添加:


再次感谢您的帮助!

我用你的脚本更改了 readObject,但我仍然只添加了最后一辆车。

我也看到了这个 post (java.io.StreamCorruptedException: invalid type code: AC) 但没能解决。所以我改变了我写对象的方式(停止将对象附加到 ObjectOutputStream)并获得了几乎不错的结果。

现在我的最终问题是 "Type safety: Unchecked cast from Object to List" on readObject()。

我认为(不确定?)这只是一个 IDE 信息,所以我想最好以正确的方式进行操作,所以我想避免使用 (@SuppressWarnings("unchecked")) over (public String toString() {) 并以最好的方式做到这一点,但我无法应用我在 Whosebug 上找到的所有解决方案。

如果你有想法,那就太完美了!

这是新的 garage.java :

public class Garage {

  private List<Vehicule> voitures = new ArrayList<Vehicule>();
  private ObjectInputStream ois;
  protected static ObjectOutputStream oos;

  public Garage(){}

  public String toString() {

    String str = "";

    // Vérification de l'existence du fichier de sauvegarde
    if(Files.notExists(Paths.get("garage.txt"))) {
      str += "Aucune voiture sauvegardée !\n";
    }

    str += "*************************\n" 
       + "* Garage OpenClassrooms *\n"
       + "*************************\n";

    // Lecture du fichier texte
    if(Files.exists(Paths.get("garage.txt"))) {
      try {
        ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(new File("garage.txt"))));

        // garage loading during instanciation :
              // Information Eclipse : 
              // Type safety: Unchecked cast from Object to List<Vehicule>
              this.voitures = (List<Vehicule>)ois.readObject();

        // Affichage des voitures enregistrées dans le fichier texte
        for(Vehicule V : this.voitures){
          str += V;
        }


              // To avoid "Type safety: Unchecked cast from Object to List<Vehicule>" :
        // those 2 solutions show identical information "Type safety: Unchecked cast from Object to List<Vehicule>"

        // First tried solution :
        // this.voitures = new new ArrayList<Vehicule>((List<Vehicule>)ois.readObject()); 

              // Second tried solution :
//              if(ois.readObject() instanceof List<?>){
//                  List<?> list = (ArrayList<?>)ois.readObject();
//                  for(Object e : list){
//                     if(e instanceof Vehicule){
//                       this.voitures = (List<Vehicule>)e;
//                     }
//                  }
//              }


        ois.close();
      } catch (FileNotFoundException e) {
        e.printStackTrace();
      } catch (ClassNotFoundException e) {
        e.printStackTrace();
      } catch (EOFException e) {
        e.printStackTrace();
      } catch (IOException e) {
        e.printStackTrace();
      } 
    }
    return str;
  }

  public void addVoiture(Vehicule voit) {

    voitures.add(voit);

    // écriture du fichier texte
    try {
      oos = new ObjectOutputStream(
          new BufferedOutputStream(
            new FileOutputStream(
              new File("garage.txt"))));

      // Enregistrement detoutes les voitures dans le fichier texte
      oos.writeObject(voitures);

      oos.close();
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } 
  }
}

添加了类型安全的解决方案:未检查的从对象到列表的转换


多亏了这个 post : Type safety: Unchecked cast

文本文件包含一个列表对象 当我启动 Garage() 时,我需要将这个 List 对象添加到现有的 List 对象中。

Eclipse 显示: 类型安全:未经检查的从对象到列表的转换

我可以在我的方法中使用:@SuppressWarnings("unchecked"),但这样做看起来更好,所以我不会忽略任何警告:

    List<?> list = (List<?>) ois.readObject();
    for(int i = 0; i < list.size(); i++) {
      this.voitures.add((Vehicule) list.get(i)); // instanciation
      str += (Vehicule) list.get(i);// Added to console message
    }       

希望对您有所帮助,对于我初学者的错误感到抱歉^^,感谢大家的帮助!

名称告诉您需要做什么:读取一个对象

未命名 read File.

换句话说:当您使用对象流时,您的代码定义了以何种顺序写入哪些对象。然后,在另一边,类似的代码必须以完全相同的顺序读取这些对象。

这就是全部。

并且您当前的代码覆盖现有文件。当你想要 "preserve" 文件内容时 - 那么你首先必须读入先前存储的对象。

每次调用 addVoiture 时,都会覆盖数据文件,而不是向其添加内容。将您的文件打开代码更改为

oos = new ObjectOutputStream(
      new BufferedOutputStream(
        new FileOutputStream(
          new File("garage.txt"), true)));

new FileOutputStream 上的额外参数 true 打开一个现有文件进行追加,而不是每次都创建一个新的空文件。请参阅文档 here

此外,由于您正在将多个 Vehicule 对象写入文件,因此您必须执行多个 readObject 调用才能再次读取所有对象。您只看到一辆车的原因是因为您只调用 readObject 一次。您需要在循环中继续调用 readObject 直到获得 EOFException:

try {
        ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(new File("garage.txt"))));
        for(;;){
            try {
               str += ((Vehicule)ois.readObject()).toString();
            } catch (EOFException eofe) {
               break;
            }
        }
        ois.close();
      }
} catch ....

通过此更改,您不再需要使用外部 try 块捕获 EOFException,因为内部 try 现在可以处理它。