Problem
I have a class in which I am passing certain parameters through the constructor and then using those parameters to make one final byte array with a proper format (header + data):
public final class Frame {
private final byte addressedCenter;
private final byte version;
private final Map<byte[], byte[]> keyDataHolder;
private final long location;
private final long locationFrom;
private final long locationOrigin;
private final byte partition;
private final byte copy;
public Frame(byte addressedCenter, byte version,
Map<byte[], byte[]> keyDataHolder, long location, long locationFrom,
long locationOrigin, byte partition, byte copy) {
this.addressedCenter = addressedCenter;
this.version = version;
this.keyDataHolder = keyDataHolder;
this.location = location;
this.locationFrom = locationFrom;
this.locationOrigin = locationOrigin;
this.partition = partition;
this.copy = copy;
}
public byte[] serialize() {
// All of the data is embedded in a binary array with fixed maximum size 70000
ByteBuffer byteBuffer = ByteBuffer.allocate(70000);
byteBuffer.order(ByteOrder.BIG_ENDIAN);
int numOfRecords = keyDataHolder.size();
int bufferUsed = getBufferUsed(keyDataHolder); // 36 + dataSize + 1 + 1 + keyLength + 8 + 2;
// header layout
byteBuffer.put(addressedCenter); // byte
byteBuffer.put(version); // byte
byteBuffer.putInt(numOfRecords); // int
byteBuffer.putInt(bufferUsed); // int
byteBuffer.putLong(location); // long
byteBuffer.putLong(locationFrom); // long
byteBuffer.putLong(locationOrigin); // long
byteBuffer.put(partition); // byte
byteBuffer.put(copy); // byte
// now the data layout
for (Map.Entry<byte[], byte[]> entry : keyDataHolder.entrySet()) {
byte keyType = 0;
byte keyLength = (byte) entry.getKey().length;
byte[] key = entry.getKey();
byte[] data = entry.getValue();
short dataSize = (short) data.length;
ByteBuffer dataBuffer = ByteBuffer.wrap(data);
long timestamp = 0;
if (dataSize > 10) {
timestamp = dataBuffer.getLong(2);
}
byteBuffer.put(keyType);
byteBuffer.put(keyLength);
byteBuffer.put(key);
byteBuffer.putLong(timestamp);
byteBuffer.putShort(dataSize);
byteBuffer.put(data);
}
return byteBuffer.array();
}
private int getBufferUsed(final Map<byte[], byte[]> keyDataHolder) {
int size = 36;
for (Map.Entry<byte[], byte[]> entry : keyDataHolder.entrySet()) {
size += 1 + 1 + 8 + 2;
size += entry.getKey().length;
size += entry.getValue().length;
}
return size;
}
}
I would like to know if it can be improved in any way. As you can see right now, I am allocating ByteBuffer
with predefined size of 70000
. Is there a better way by which I can allocate the size I am using while making ByteBuffer
instead of using a hardcoded 70000
?
Solution
A minor aesthetics advice
You can save some typing by using the method chains over ByteBuffer
:
private int getBufferCapacity(final Map<byte[], byte[]> keyDataHolder) {
int size = 36;
for (Map.Entry<byte[], byte[]> entry : keyDataHolder.entrySet()) {
size += 1 + 1 + 8 + 2;
size += entry.getKey().length;
size += entry.getValue().length;
}
return size;
}
public byte[] serialize2() {
ByteBuffer byteBuffer = ByteBuffer.allocate(getBufferCapacity(keyDataHolder))
.order(ByteOrder.BIG_ENDIAN);
// Use chaining:
byteBuffer.put(addressedCenter)
.put(version)
.putInt(keyDataHolder.size())
.putInt(getBufferUsed(keyDataHolder))
.putLong(location)
.putLong(locationFrom)
.putLong(locationOrigin)
.put(partition)
.put(copy);
for (Map.Entry<byte[], byte[]> entry : keyDataHolder.entrySet()) {
byte keyType = 0;
byte[] key = entry.getKey();
byte[] value = entry.getValue(); // A map mapping is often called a
// key/value -pair.
byte keyLength = (byte) key.length;
short valueLength = (short) value.length;
ByteBuffer dataBuffer = ByteBuffer.wrap(value);
// Short cut:
long timestamp = valueLength > 10 ? dataBuffer.getLong(2) : 0;
// Chaining:
byteBuffer.put(keyType)
.put(keyLength)
.put(key)
.putLong(timestamp)
.putShort(valueLength)
.put(value);
}
return byteBuffer.array();
}
Also, when you deal with maps, the conventional terminology for a mapping is a key/value-pair. For this reason, I would rewrite
byte[] key = entry.getKey();
byte[] data = entry.getValue();
to
byte[] key = entry.getKey();
byte[] value = entry.getValue();
Then, later on in the code, I would change
byte keyLength = (byte) entry.getKey().length;
with
byte keyLength = key.length;
Instead of
long timestamp = 0;
if (dataSize > 10) {
timestamp = dataBuffer.getLong(2);
}
you can write an one-liner:
long timestamp = valueLength > 10 ? dataBuffer.getLong(2) : 0;
Finally, since you know your file format, you should be able to come up with a function that computes the exact size of the file for particular set of data; call it foo
. Now, you would normally do:
ByteBuffer byteBuffer = ByteBuffer.allocate(foo(keyDataHolder));
Hope that helps.