5

I am puzzled by the behavior of ObjectOutputStream. It seems like it has an overhead of 9 bytes when writing data. Consider the code below:

float[] speeds = new float[96];
float[] flows = new float[96];

//.. do some stuff here to fill the arrays with data

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos=null;
try {
    oos = new ObjectOutputStream(baos);
    oos.writeInt(speeds.length);
    for(int i=0;i<speeds.length;i++) {
        oos.writeFloat(speeds[i]);
    }
    for(int i=0;i<flows.length;i++) {
        oos.writeFloat(flows[i]);
    }
    oos.flush();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        if(oos!=null) {
            oos.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

byte[] array = baos.toByteArray();

The length of the array is always 781, while I would expect it to be (1+96+96)*4 = 772 bytes. I can't seem to find where the 9 bytes go.

Thanks!

--edit: added if(oos!=null) { ... } to prevent NPE

hinsbergen
  • 147
  • 2
  • 11
  • 2
    Like most file formats, there's a header and version number. If you're just writing primitives, `ObjectOutputStream` isn't a great solution. / Also `acquire(); try { use(); } finally { release(); }` or in Java SE 7 `try (Type resource = acquire()) { use(); }`. You've got a potential NPE there, as well as poor error handling. – Tom Hawtin - tackline Feb 27 '12 at 13:55
  • Hi Tom, you're right about the nullpointer. Fixed it in the code. – hinsbergen Feb 27 '12 at 14:12
  • 1
    The NPE is just side-effect of the awkward resource handling. It's incredibly common to mess up once you decide against clear code. Even for "The CERT Oracle Secure Coding Standard for Java" I had to repeatedly point out flaws in the code. Keep it simple. / Oh, and don't be afraid of looking at the raw serialised data in a hex editor/viewer! – Tom Hawtin - tackline Feb 27 '12 at 14:44

4 Answers4

3

ObjectOutputStream is used to serialize objects. You shouldn't make any assumptions how the data is stored.

If you want to store just the raw data use DataOutputStream instead.

Piotr Praszmo
  • 17,928
  • 1
  • 57
  • 65
2

The ObjectOutputStream writes a header at the beginning.

You can eliminate this header by subclassing ObjectOutputStream and implementing writeStreamHeader().

Andy Thomas
  • 84,978
  • 11
  • 107
  • 151
0

The JavaDoc for ObjectOutputStream tells you:

Primitive data, excluding serializable fields and externalizable data, is written to the ObjectOutputStream in block-data records. A block data record is composed of a header and data. The block data header consists of a marker and the number of bytes to follow the header. Consecutive primitive data writes are merged into one block-data record. The blocking factor used for a block-data record will be 1024 bytes. Each block-data record will be filled up to 1024 bytes, or be written whenever there is a termination of block-data mode. Calls to the ObjectOutputStream methods writeObject, defaultWriteObject and writeFields initially terminate any existing block-data record.

So the blocking stuff might be your missing overhead.

A.H.
  • 63,967
  • 15
  • 92
  • 126
0

Java's serialization stream begins with a 4-byte header (2-byte "magic number" followed by a 2-byte version). The header is followed by a sequence of block-data and objects entries. There are two kinds of blocks-data entries: "short" and "long." Short blocks have a 2-byte overhead per block and the block can be at most 255 bytes long. Long blocks have a 5-byte overhead but they can be up to 4 GB in length. How long the "long" block can be in practice depends on the size of ObjectOutputStream's internal buffers.

In this case you only have one long data-block entry, so the overhead you see is 4 bytes from the stream header and 5 from the data block, 9 bytes in total.

You'll find the full documentation here: http://docs.oracle.com/javase/7/docs/platform/serialization/spec/protocol.html

Joni
  • 108,737
  • 14
  • 143
  • 193