web-dev-qa-db-fra.com

Ajout à un ObjectOutputStream

N'est-il pas possible d'ajouter à un ObjectOutputStream?

J'essaie d'ajouter à une liste d'objets. L'extrait de code suivant est une fonction qui est appelée chaque fois qu'un travail est terminé.

FileOutputStream fos = new FileOutputStream
           (preferences.getAppDataLocation() + "history" , true);
ObjectOutputStream out = new ObjectOutputStream(fos);

out.writeObject( new Stuff(stuff) );
out.close();

Mais quand j'essaye de le lire, je n'ai que le premier dans le fichier. Ensuite, je reçois Java.io.StreamCorruptedException.

Pour lire j'utilise

FileInputStream fis = new FileInputStream
        ( preferences.getAppDataLocation() + "history");
ObjectInputStream in = new ObjectInputStream(fis);    

try{
    while(true)
        history.add((Stuff) in.readObject());
}catch( Exception e ) { 
    System.out.println( e.toString() );
}

Je ne sais pas combien d'objets seront présents donc je lis alors qu'il n'y a pas d'exceptions. D'après ce que Google dit, ce n'est pas possible. Je me demandais si quelqu'un connaissait un moyen?

55
Hamza Yerlikaya

Voici l'astuce: sous-classe ObjectOutputStream et remplacez la méthode writeStreamHeader:

public class AppendingObjectOutputStream extends ObjectOutputStream {

  public AppendingObjectOutputStream(OutputStream out) throws IOException {
    super(out);
  }

  @Override
  protected void writeStreamHeader() throws IOException {
    // do not write a header, but reset:
    // this line added after another question
    // showed a problem with the original
    reset();
  }

}

Pour l'utiliser, il suffit de vérifier si le fichier historique existe ou non et d'instancier soit ce flux modifiable (dans le cas où le fichier existe = nous ajoutons = nous ne voulons pas d'en-tête) ou le flux d'origine (dans le cas où le fichier n'existe pas = nous avons besoin d'un en-tête).

Modifier

Je n'étais pas satisfait du premier nom de la classe. Celui-ci est meilleur: il décrit le "à quoi ça sert" plutôt que le "comment c'est fait"

Modifier

Modification du nom une fois de plus, pour clarifier, que ce flux est uniquement destiné à être ajouté à un fichier existant. Il ne peut pas être utilisé pour créer un fichier nouveau avec des données d'objet.

Modifier

Ajout d'un appel à reset() après cette question a montré que la version d'origine qui surpassait simplement writeStreamHeader pour être un no-op pouvait dans certaines conditions créer un flux qui ne pouvait pas pas lu.

77
Andreas_D

Comme le dit API , le constructeur ObjectOutputStream écrit l'en-tête du flux de sérialisation dans le flux sous-jacent. Et cet en-tête ne devrait être qu'une seule fois, au début du fichier. Donc, appeler

new ObjectOutputStream(fos);

plusieurs fois sur le FileOutputStream qui fait référence au même fichier écrira plusieurs fois l'en-tête et corrompra le fichier.

13
Tadeusz Kopec

En raison du format précis du fichier sérialisé, l'ajout le corrompra en effet. Vous devez écrire tous les objets dans le fichier dans le cadre du même flux, sinon il se bloquera lors de la lecture des métadonnées du flux lorsqu'il attend un objet.

Vous pouvez lire Serialization Specification pour plus de détails, ou (plus facile) lire ce fil où Roedy Green dit essentiellement ce que je viens de dire.

7
Michael Myers

Le moyen le plus simple d'éviter ce problème est de laisser OutputStream ouvert lorsque vous écrivez les données, au lieu de le fermer après chaque objet. L'appel de reset() peut être conseillé pour éviter une fuite de mémoire.

L'alternative serait également de lire le fichier comme une série de ObjectInputStreams consécutifs. Mais cela vous oblige à compter le nombre d'octets que vous lisez (cela peut être implémenté avec un FilterInputStream), puis fermez le InputStream, ouvrez-le à nouveau, ignorez autant d'octets et ensuite encapsulez-le dans un ObjectInputStream ().

6
Michael Borgwardt

Que diriez-vous avant chaque fois que vous ajoutez un objet, lisez et copiez toutes les données actuelles dans le fichier, puis écrasez-les toutes ensemble dans le fichier.

0
user4147874

J'ai étendu la solution acceptée pour créer une classe qui peut être utilisée à la fois pour ajouter et créer un nouveau fichier.

import Java.io.DataOutputStream;
import Java.io.IOException;
import Java.io.ObjectOutputStream;
import Java.io.OutputStream;

public class AppendableObjectOutputStream extends ObjectOutputStream {

    private boolean append;
    private boolean initialized;
    private DataOutputStream dout;

    protected AppendableObjectOutputStream(boolean append) throws IOException, SecurityException {
        super();
        this.append = append;
        this.initialized = true;
    }

    public AppendableObjectOutputStream(OutputStream out, boolean append) throws IOException {
        super(out);
        this.append = append;
        this.initialized = true;
        this.dout = new DataOutputStream(out);
        this.writeStreamHeader();
    }

    @Override
    protected void writeStreamHeader() throws IOException {
        if (!this.initialized || this.append) return;
        if (dout != null) {
            dout.writeShort(STREAM_MAGIC);
            dout.writeShort(STREAM_VERSION);
        }
    }

}

Cette classe peut être utilisée comme remplacement étendu direct d'ObjectOutputStream. Nous pouvons utiliser la classe comme suit:

import Java.io.File;
import Java.io.FileNotFoundException;
import Java.io.FileOutputStream;
import Java.io.IOException;

public class ObjectWriter {

    public static void main(String[] args) {

        File file = new File("file.dat");
        boolean append = file.exists(); // if file exists then append, otherwise create new

        try (
            FileOutputStream fout = new FileOutputStream(file, append);
            AppendableObjectOutputStream oout = new AppendableObjectOutputStream(fout, append);
        ) {
            oout.writeObject(...); // replace "..." with serializable object to be written
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}
0
Pratanu Mandal