javaobjectoutputstream

ObjectOutputStream writes same instances differently depending on how i open stream


For the unknown reason, instances written into the same objectOutputStream and instances written separately (If I open objectOutputStream on each iteration) both create two different files.

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filepath1));
for (User user : users)
{
    objectOutputStream.writeObject(user);
}

objectOutputStream.close();

for (User user : users)
{
    objectOutputStream = new ObjectOutputStream(new FileOutputStream(filepath2, true));
    objectOutputStream.writeObject(user);
    objectOutputStream.close();
}

So when I read files in a loop like this it works fine only for the first file.

for( int i = 0; i< users.length; i++)
{
    readUsers[i] = (User)objectInputStream.readObject();
}

Reading the second file gives me ONE correctly read user which is followed by an Exception java.io.StreamCorruptedException: invalid type code: AC. I've inspected the content of these files and it seems there's an excessive data at the start of each record in the second one (It takes almost twice as much space as the first one). So how to combine second way of writing instances to file and read them in a simple loop afterwards?


Solution

  • Java serialization, implemented by Object{Output,Input}Stream, in addition to metadata and data for each object, has a stream header that only occurs once and not for each object. Normally in a file this amounts to a file header. If you want to put multiple streams in one file, you must manage the stream boundaries yourself:

    //nopackage
    import java.io.*;
    
    public class SO71319428MultipleSerial {
      public static void main (String[] args) throws Exception {
        User[] a = { new User("Alice",1), new User("Bob",2), new User("Carol",3) };
        for( User u : a )
          try( ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(filename,true)) ){
            oo.writeObject(u);
          }
        System.out.println("reading "+new File(filename).length());
        try( InputStream fi = new FileInputStream(filename) ){
          for( int i = 0; i < 3; i++ ){
            ObjectInputStream oi = new ObjectInputStream(fi);
            System.out.println( oi.readObject() );
            // DON'T close because that closes the underlying FileInputStream; just leak instead
          }
        }
      }
      public static String filename = "SO71319428.out";
    
      static class User implements Serializable {
        String name; int id;
        public User(String name, int id){ this.name=name; this.id=id; }
        public String toString(){ return name+" is #"+id; }
      }
    }
    ->
    reading 283
    Alice is #1
    Bob is #2
    Carol is #3
    

    This is less flexible than the single-stream (with multiple objects) approach, because that allows you to write any sequence of objects whose types and end can be recognized when reading without any help; with the multiple-stream approach your code must be able to determine when a stream ends and needs to be restarted.