1

I am designing an archive format(Just for fun) in Java using this template-

First 4 bytes: Number of files in the archive
Next 4 bytes: Number of bytes in the filename
Next N bytes: Filename
Next 10 bytes: Number of bytes in the file
Next N bytes: File contents

from PHP Safe way to download mutliple files and save them.

I am having on trouble with finding the values of the number of files etc. but I don't know how to expand an integer into 4 bytes.

Is it similar to this- How do I truncate a java string to fit in a given number of bytes, once UTF-8 encoded?

Community
  • 1
  • 1
liamzebedee
  • 14,010
  • 21
  • 72
  • 118

2 Answers2

5

Use a DataOutput/DataInput implementation to write/read that format, it does most of the work for you. The classical implementations are DataOutputStream and DataInputStream:

DataOutputStream dos = new DataOutputStream(outputStream);
dos.writeInt(numFiles);
// for each file name
byte[] fn = fileName.getBytes("UTF-8"); // or whichever encoding you chose
dos.writeInt(fn.length);
dos.write(fn);
// ...

Reading works pretty much the same.

Note that these use big endian. You'll have to check (or specify) if your format uses big- or little-endian.

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
  • Another option would be to use `ByteBuffer`. It's not as easy to use, but it's good if you want to do multiple passes over some sections of data without copying. – Daniel Lubarov May 04 '11 at 09:45
  • @Daniel: that's true. [`ByteBuffer`](http://download.oracle.com/javase/6/docs/api/java/nio/ByteBuffer.html) also has the advantage of having [configurable byte order/endianness](http://download.oracle.com/javase/6/docs/api/java/nio/ByteBuffer.html#order(java.nio.ByteOrder)). – Joachim Sauer May 04 '11 at 09:55
3

You can convert an int into 4 bytes like this:

public byte[] getBytesForInt(int value) {
    byte[] bytes = new byte[4];
    bytes[0] = (byte) ((value >> 24) & 0xFF);
    bytes[1] = (byte) ((value >> 16) & 0xFF);
    bytes[2] = (byte) ((value >> 8) & 0xFF);
    bytes[3] = (byte) (value & 0xFF);
    return bytes;
}

This would put them in big-endian order as often used for transport (see Endianness). Alternatively if you're already dealing with an OutputStream you could wrap it with a DataOutputStream and just use writeInt(). For example as per your template:

FileOutputStream fileOut = new FileOutputStream("foo.dat");
DataOutputStream dataOut = new DataOutputStream(fileOut);
dataOut.writeInt(numFiles);
dataOut.writeInt(numBytesInName);
dataOut.writeUTF(filename);
dataOut.writeLong(numBytesInFile);
dataOut.write(fileBytes);

Note that the writeLong() is actually 8 bytes. I'm not sure why you'd want to use 10 and I imagine 8 from a long is plenty.

WhiteFang34
  • 70,765
  • 18
  • 106
  • 111
  • In your code I think there is an error on line 2. Unexpected int keyword? – liamzebedee May 04 '11 at 09:36
  • I understand that a byte is 8 bits and an integer in java is 32 bits. So does that mean I don't even need to go through all of this and just write the integer since an integer is 4 bytes(32 bits) – liamzebedee May 04 '11 at 09:40
  • 1
    @Liam: yes, a Java `int` holds 4 bytes worth of data, so writing it directly will have the same effect. **However** a normal `OutputStream` has no method to directly write an `int` (don't be fooled with [this method](http://download.oracle.com/javase/6/docs/api/java/io/OutputStream.html#write(int)), read its documentation to see why). Therefore you need some other way to write it: Either convert it to `byte[]` and write that or get a class that can write `int` values). – Joachim Sauer May 04 '11 at 09:44
  • 1
    Yeah, `writeInt()` should do what you need. No need to convert to bytes manually. I provided the byte example since your question asked for it :) – WhiteFang34 May 04 '11 at 09:45