Comment puis-je append à un java.io.ObjectStream existant?

Pour le moment, je vais avoir java.io.StreamCorruptedException quand j’essaye d’append un object. J’ai cherché sur Internet un moyen de surmonter ce problème. La réponse que j’ai trouvée jusqu’à présent est que cela ne peut pas être fait. Une solution à ce problème consiste à écrire les objects dans une liste, puis à écrire la liste dans le fichier.

Mais je dois écraser ce fichier à chaque fois que j’ajoute de nouveaux objects. Cela ne semble pas être la solution optimale en heures supplémentaires.

Est-il possible d’append des objects à un stream d’objects existant?

C’est en fait assez facile à faire. Lorsque vous ajoutez un stream existant, vous devez utiliser une sous-classe de ObjectOutStream qui remplace writeStreamHeader afin writeStreamHeader deuxième en-tête ne soit écrit au milieu du fichier. Par exemple

 class NoHeaderObjectOutputStream extends ObjectOutputStream { public NoHeaderObjectOutputStream(OutputStream os) { super(os); } protected void writeStreamHeader() {} } 

Ensuite, utilisez simplement un ObjectInputStream standard pour lire le fichier entier.

Le meilleur article que j’ai trouvé sur ce sujet est: http://codify.flansite.com/2009/11/java-serialization-appending-objects-to-an-existing-file/

La “solution” qui remplace ObjectOutputStream est tout simplement fausse. Je viens juste de finir d’enquêter sur un bug causé par cela (perdre deux jours précieux). Non seulement le fichier sérialisé était parfois corrompu, mais il était même possible de lire sans exception et en fournissant des données parasites (champs de mélange). Pour les incroyants, je joins un code exposant le problème:

 import java.io.*; import java.util.HashMap; import java.util.Map; public class Main { public static void main(Ssortingng[] args) throws Exception { File storageFile = new File("test"); storageFile.delete(); write(storageFile, getO1()); write(storageFile, getO2()); write(storageFile, getO2()); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(storageFile)); read(ois, getO1()); read(ois, getO2()); read(ois, getO2()); } private static void write(File storageFile, Map o) throws IOException { ObjectOutputStream oos = getOOS(storageFile); oos.writeObject(o); oos.close(); } private static void read(ObjectInputStream ois, Map expected) throws ClassNotFoundException, IOException { Object actual = ois.readObject(); assertEquals(expected, actual); } private static void assertEquals(Object o1, Object o2) { if (!o1.equals(o2)) { throw new AssertionError("\n expected: " + o1 + "\n actual: " + o2); } } private static Map getO1() { Map nvps = new HashMap(); nvps.put("timestamp", "1326382770000"); nvps.put("length", "246"); return nvps; } private static Map getO2() { Map nvps = new HashMap(); nvps.put("timestamp", "0"); nvps.put("length", "0"); return nvps; } private static ObjectOutputStream getOOS(File storageFile) throws IOException { if (storageFile.exists()) { // this is a workaround so that we can append objects to an existing file return new AppendableObjectOutputStream(new FileOutputStream(storageFile, true)); } else { return new ObjectOutputStream(new FileOutputStream(storageFile)); } } private static class AppendableObjectOutputStream extends ObjectOutputStream { public AppendableObjectOutputStream(OutputStream out) throws IOException { super(out); } @Override protected void writeStreamHeader() throws IOException { // do not write a header } } } 

Comme indiqué dans cet article, vous pouvez utiliser l’une des solutions suivantes:

Solution n ° 1: Faux fichiers multiples dans un seul stream

Ecrivez votre “transaction” sur un ByteArrayOutputStream, puis écrivez la longueur et le contenu de ce ByteArrayOutputStream dans un fichier via le DataOutputStream.

Solution n ° 2: rouvrir et ignorer

Une autre solution consiste à enregistrer la position du fichier en utilisant:

 long pos = fis.getChannel().position(); 

fermez le fichier, rouvrez-le et passez à cette position avant de lire la transaction suivante.

Un grand merci à George Hategan pour le problème qui expose le code. Je l’ai examiné pendant un moment aussi. Ensuite, ça m’a frappé. Si vous utilisez un object ObjectOutputStream avec une substitution de la méthode writeStreamHeader () pour écrire des données, vous devez utiliser ObjectInputStream avec une sous-classe parallèle avec un remplacement de la méthode readStreamHeader () pour lire les données. Bien sûr, nous pouvons zigzaguer entre différentes implémentations d’objects d’écriture et de lecture d’objects, mais tant que nous utilisons les paires de sous-classes correspondantes dans le processus d’écriture / lecture, tout ira bien (espérons-le). À M.

 import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; public class SerializationDemo { public static void main(Ssortingng[] args) throws Exception { File storageFile = new File("test.ser"); storageFile.delete(); write(storageFile, getO1()); write(storageFile, getO2()); write(storageFile, getO2()); FileInputStream fis = new FileInputStream(storageFile); read(fis, getO1()); read(fis, getO2()); read(fis, getO2()); fis.close(); } private static void write(File storageFile, Map o) throws IOException { ObjectOutputStream oos = getOOS(storageFile); oos.writeObject(o); oos.flush(); oos.close(); } private static void read(FileInputStream fis, Map expected) throws ClassNotFoundException, IOException { Object actual = getOIS(fis).readObject(); assertEquals(expected, actual); System.out.println("read serialized " + actual); } private static void assertEquals(Object o1, Object o2) { if (!o1.equals(o2)) { throw new AssertionError("\n expected: " + o1 + "\n actual: " + o2); } } private static Map getO1() { Map nvps = new HashMap(); nvps.put("timestamp", "1326382770000"); nvps.put("length", "246"); return nvps; } private static Map getO2() { Map nvps = new HashMap(); nvps.put("timestamp", "0"); nvps.put("length", "0"); return nvps; } private static ObjectOutputStream getOOS(File storageFile) throws IOException { if (storageFile.exists()) { // this is a workaround so that we can append objects to an existing file return new AppendableObjectOutputStream(new FileOutputStream(storageFile, true)); } else { return new ObjectOutputStream(new FileOutputStream(storageFile)); } } private static ObjectInputStream getOIS(FileInputStream fis) throws IOException { long pos = fis.getChannel().position(); return pos == 0 ? new ObjectInputStream(fis) : new AppendableObjectInputStream(fis); } private static class AppendableObjectOutputStream extends ObjectOutputStream { public AppendableObjectOutputStream(OutputStream out) throws IOException { super(out); } @Override protected void writeStreamHeader() throws IOException { // do not write a header } } private static class AppendableObjectInputStream extends ObjectInputStream { public AppendableObjectInputStream(InputStream in) throws IOException { super(in); } @Override protected void readStreamHeader() throws IOException { // do not read a header } } } 

Vous devez créer un nouveau ObjectInputStream pour correspondre à chaque ObjectOutputStream . Je ne connais pas le moyen de transférer l’état d’un ObjectInputStream complet à un ObjectOutputStream (sans une réimplémentation complète, ce qui est un peu délicat en Java pur)