The best article I've found on this topic is:
http://codify.flansite.com/2009/11/java-serialization-appending-objects-to-an-existing-file/
The "solution" that overrides ObjectOutputStream is simply wrong. I've just finished investigating a bug that was caused by that (wasting two precious days). Not only that it would sometimes corrupt the serialized file but even managed to read without throwing exceptions and in the end providing garbage data (mixing fields). For those in disbelief, I'm attaching some code that exposes the problem:
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] 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<String, String> o) throws IOException {
ObjectOutputStream oos = getOOS(storageFile);
oos.writeObject(o);
oos.close();
}
private static void read(ObjectInputStream ois, Map<String, String> 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<String, String> getO1() {
Map<String, String> nvps = new HashMap<String, String>();
nvps.put("timestamp", "1326382770000");
nvps.put("length", "246");
return nvps;
}
private static Map<String, String> getO2() {
Map<String, String> nvps = new HashMap<String, String>();
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
}
}
}
As stated in that article, you can use one of the following solutions:
Solution #1: Fake Multiple file in a Single Stream
...
Write your “transaction” to a ByteArrayOutputStream, then write the
length and contents of this ByteArrayOutputStream to a file via the
DataOutputStream.
Solution #2: Reopen and Skip
Another solution involves saving the file position using:
long pos = fis.getChannel().position();
closing the file, reopening the file, and skipping to this position
before reading the next transaction.