1

I want to serialize a Hashtable to a file using ObjectOutputStream.writeObject(), but I want the writer to always overwrite any existing object such that only a single object exists:

FileOutputStream fout = new FileOutputStream( f);
ObjectOutputStream writer = new ObjectOutputStream(fout);
writer.writeObject( hashtable);
writer.flush();

The hastable is updated intermittently at runtime, so I use a helper method to persist its state:

private void persistObject( Hashtable ht){
   writer.writeObject( ht);
   writer.flush();
}

The problem is every time I call writeObject(), a new hastable is appended to the file; Is there any way to just overwrite whatever is in the file so only a single object is ever persisted?

raffian
  • 31,267
  • 26
  • 103
  • 174

4 Answers4

8

Easiest way would be to close and reopen the file, just re-run this code inside your persistObject() method:

private void persistObject( Hashtable ht){
    try (ObjectOutputStream writer = new ObjectOutputStream(new FileOutputStream(f))) {
        writer.writeObject( hashtable);
        writer.flush();
        writer.close();
    }
}
raffian
  • 31,267
  • 26
  • 103
  • 174
Brad Peabody
  • 10,917
  • 9
  • 44
  • 63
  • (...don't forget to close the stream...) – MadProgrammer Aug 16 '13 at 02:27
  • Yeah, I thought about that, but I have to keep the file open. It's a server, the hashtable maintains critical state that changes often; want to avoid the overhead of opening/closing the file. – raffian Aug 16 '13 at 02:28
  • 2
    Gotcha - IMO that overhead is probably not worth worrying about. On modern OSes it's going to be quite small, and often the kernel does a good job of caching information related to files that are used often (i.e. if you do this a lot, the overhead of opening and closing the file isn't much). How big is the hashtable (roughly)? – Brad Peabody Aug 16 '13 at 02:31
  • It's not big, few thousand objects – raffian Aug 16 '13 at 02:33
  • @StevenSchlansker Np, only issue is the FileOutputStream doesn't get explicitly close()ed like that... – Brad Peabody Aug 16 '13 at 04:13
  • @StevenSchlansker better practice to use explicit `flush()` and `close()`. – raffian Aug 16 '13 at 04:35
  • I don't agree, but I'm not going to get into an edit war over it... OOS.close() will close the FileOutputStream for you. An explicit flush is not necessary. It's harmless in this case, but can be harmful in others (e.g. HttpOutputStream) – Steven Schlansker Aug 16 '13 at 16:21
  • 1
    I agree with @StevenSchlansker on this one - close() is necessary, flush() is not. This question covers it: http://stackoverflow.com/questions/2732260 – Brad Peabody Aug 16 '13 at 17:00
  • Hold up - doesn't `try-with-resources` auto-close streams and stuff? isn't `close()` supplied automatically after end of the `try` block? – C0nverter Mar 12 '20 at 12:26
1

If you want to discard file contents and start writing at the beginning of the file again, you can obtain it's channel and reposition the content. The problem is, ObjectStreams uses some internal headers, and if we want to "reset" the stream we need to account for that.

Just after instantiating the ObjectOutputStream, store the initial position (writeStreamHeader writes two shorts - which consumes 4 bytes, but better be generic than sorry) so that you can position the channel without overwriting the header.

final long initialPosition = fout.getChannel().position();

Then, every time you have to write something, skip the initial bytes (by positioning the channel), reset the stream and write your object:

//java 1.7
private void persistObject(Hashtable ht){
   fout.getChannel().position(initialPosition);
   fout.reset();
   writer.writeObject(ht);
   writer.flush();
}
//java 1.6
private void persistObject(Hashtable ht){
   fout.getChannel().position(initialPosition);
   fout.getChannel().truncate(initialPosition);
   writer.writeObject(ht);
   writer.flush();
}

As @bgp mentioned, I don't think that opening / closing the file really introduces that much overhead... Also I don't think that truncating and flushing to disk at every write is efficient, or that keeping file streams open for long periods of time is a good practice (or safe), still it was quite fun to hack the ObjectOutputStream.

raffian
  • 31,267
  • 26
  • 103
  • 174
Anthony Accioly
  • 21,918
  • 9
  • 70
  • 118
  • Nice - was looking for that, I haven't tested but this looks like what @raffian is asking for. – Brad Peabody Aug 16 '13 at 02:35
  • @raffian. Exactly, truncate or reset the Channel position just before writing the Object. – Anthony Accioly Aug 16 '13 at 02:40
  • 1
    I write to the file using your approach, but on startup I read the file; this wasn't part of the question, but if your method works, the contents should be readable using `readObject()`. I'll play around with it, – raffian Aug 16 '13 at 03:00
  • 1
    This won't work. The object stream header will be removed or overwritten. – user207421 Aug 16 '13 at 03:29
  • I learned something new, even if it didn't work in this case :-D – raffian Aug 16 '13 at 05:43
  • @EJP. You were right, fixed the code so it takes the stream header into consideration. Raffian sorry for jumping the gun, you were right. But now you have one "slick" solution. – Anthony Accioly Aug 16 '13 at 05:44
  • @raffian, take my updated code for a ride, now it is working. – Anthony Accioly Aug 16 '13 at 05:47
  • 1
    damn, you don't quit, do you? i'll play with this tomorrow, ty – raffian Aug 16 '13 at 05:55
  • 1
    Worked nice, I update your answer to reflect 1.6 and 1.7 differences, thx again. – raffian Aug 16 '13 at 22:14
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/35631/discussion-between-anthony-accioly-and-raffian) – Anthony Accioly Aug 16 '13 at 23:26
  • The issue with your original code was, without `truncate()`, there's data left over if a small object is written over a larger one. `truncate()` helps to compacts and gave that extra efficiency. great solution overall, exactly what I needed – raffian Aug 17 '13 at 00:56
1

You should open / write / close ObjectOutputStream in persistObject. You should not truncate file at position 0. ObjectOuputStream has a complex structure, not only objects data but a header, class descriptors etc. Truncating will destroy it resulting in StreamCorruptedException when attempting to read it.

Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
0

You only have to close the file when you save the Object and open it when retriving object.

FileOutputStream fout = new FileOutputStream( f);
ObjectOutputStream writer = new ObjectOutputStream(fout);
writer.writeObject( hashtable);
writer.close();