Creating and reading from a custom file type in Java
I'm creating an installer and there are some resource files (.xmls, .zip files, a .jar file, etc) that must be read during installation, but I'd开发者_如何学运维 like to pack them into a custom file (i.e., a .dat file) so that when distributed, users don't get to mess around with them too much. The problem is that the installer must be written in Java and I've never done this sort of thing before in any programming language. Is it even possible? If so, how can I pack it in a way that can be read by my Java app afterwards and how can I make my Java app read it?
There are a lot of questions you'll need to answer for yourself about the requirements of this filetype. Does it need to be compressed? Encrypted? Does it need to support random access reading, or is stream-reading good enough?
I could be wrong, but I don't think that's what you're asking in this question. If I'm reading you correctly, I think you're asking "how do I read & write arbitrary file data?"
So that's the question I'll answer. Update your question if that's not quite what you're looking for.
Custom filetypes can easily be implemented using the DataInputStream and DataOutputStream classes. These will let you read & write primitives (boolean, char, byte, int, long, float, double) to the stream. There are also some convenience methods for reading & writing UTF-8 encoded Strings, byte-arrays, and a few other goodies.
Let's get started.
For the sake of argument, let's pretend that all my data elements are byte arrays. And each of them has a name. So my filetype can be modeled logically as a Map<String, byte[]>
. I'd implement my custom filetype reader/writer class like this:
public class MyFileTypeCodec {
public static void writeToFile(File f, Map<String, byte[]> map)
throws IOException {
// Create an output stream
DataOutputStream stream = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(f))
);
// Delegate writing to the stream to a separate method
writeToStream(stream, map);
// Always be sure to flush & close the stream.
stream.flush();
stream.close();
}
public static Map<String, byte[]> readFromFile(File f)
throws IOException {
// Create an input stream
DataInputStream stream = new DataInputStream(
new BufferedInputStream(new FileInputStream(f))
);
// Delegate reading from the stream to a separate method
Map<String, byte[]> map = readFromStream(stream);
// Always be sure to close the stream.
stream.close();
return map;
}
public static void writeToStream(DataOutputStream stream, Map<String, byte[]> map)
throws IOException {
// First, write the number of entries in the map.
stream.writeInt(map.size());
// Next, iterate through all the entries in the map
for (Map.Entry<String, byte[]> entry : map.entrySet()) {
// Write the name of this piece of data.
stream.writeUTF(entry.getKey());
// Write the data represented by this name, making sure to
// prefix the data with an integer representing its length.
byte[] data = entry.getValue();
stream.writeInt(data.length);
stream.write(data);
}
}
public static Map<String, byte[]> readFromStream(DataInputStream stream)
throws IOException {
// Create the data structure to contain the data from my custom file
Map<String, byte[]> map = new HashMap<String, byte[]>();
// Read the number of entries in this file
int entryCount = stream.readInt();
// Iterate through all the entries in the file, and add them to the map
for (int i = 0; i < entryCount; i++) {
// Read the name of this entry
String name = stream.readUTF();
// Read the data associated with this name, remembering that the
// data has an integer prefix representing the array length.
int dataLength = stream.readInt();
byte[] data = new byte[dataLength];
stream.read(data, 0, dataLength);
// Add this entry to the map
map.put(name, data);
}
return map;
}
}
The basic idea is that you can write any data to an output stream (and read it back again) if you can represent that data as some combination of primitives. Arrays (or other collections) can be prefixed with their length, like I've done here. Or you can avoid writing the length prefix if you put a TERMINUS sentinel at the end (kind of like null-terminated strings).
I always use this kind of setup when I implement a custom filetype codec, with file IO methods delegating down into stream IO methods. Usually, I discover later that the object I'm reading & writing from this stream could be just as easily written into some larger & more complex file.
So I might have a SuperFancyCodec
for reading/writing the data for my whole system, and it calls down into my TinySpecialPurposeCodec
. As long as the stream reading & writing methods are public, then I can assemble new filetypes using a component-oriented methodology.
The extension usually have very little to do with how the file is interpreted.
If you'd like to have just config.dat
instead of config.xml
you just rename the file. (You'd typically give an xml-parser an InputStream
or a Reader
as input, which may read any file, regardless of extension)
If the problem you're describing is about combining multiple files, (.zip, .jar, etc) into a single .dat file, you could for instance zip them together, and name the zip file with a .dat extension. Java has good support for zip-files and can handle the zip file regardless of filename / extension.
- Related link: Reading the Contents of a ZIP File
When creating/reading files in Java (or anything else), the file extension is not strictly tyed to the actual structure of the file's data. If I wanted, I could make an XML files file.gweebz
. OS's and applications would not know what to do with it, but once opened, it would be clear that it is XML.
That being said, it is often good to follow the conventions already established and usually .dat
files are files in a binary format. You can use .dat for what you want, but be warned that some users may have OS bindings for the file type and clicking on your file may cause different-than-expected behavior on their systems.
As for how to do it in Java. Grabbing a file handle in Java is easy...
File myFile = new File("/dir/file.gweebz");
It is as simple as that and you can name it whatever you want. You will need other classes to write and read from the file or to do compression, but I will assume you know how to do that. If not, this site will have the answer.
精彩评论