在 Java 中创建和读取自定义文件类型

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5995899/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 13:53:05  来源:igfitidea点击:

Creating and reading from a custom file type in Java

java

提问by makoshichi

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?

我正在创建一个安装程序,在安装过程中必须读取一些资源文件(.xmls、.zip 文件、.jar 文件等),但我想将它们打包成一个自定义文件(即.dat 文件),以便在分发时,用户不会过多地使用它们。问题是安装程序必须用 Java 编写,我以前从未用任何编程语言做过这种事情。甚至有可能吗?如果是这样,我如何以一种可以被我的 Java 应用程序读取的方式打包它,以及如何让我的 Java 应用程序读取它?

回答by benjismith

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.

使用 DataInputStream 和 DataOutputStream 类可以轻松实现自定义文件类型。这些将允许您向流读取和写入原语(boolean、char、byte、int、long、float、double)。还有一些方便的方法来读写 UTF-8 编码的字符串、字节数组和其他一些好东西。

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:

为了便于论证,让我们假设我的所有数据元素都是字节数组。他们每个人都有一个名字。所以我的文件类型可以逻辑地建模为Map<String, byte[]>. 我会像这样实现我的自定义文件类型读取器/写入器类:

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).

基本思想是,如果您可以将数据表示为某种原语组合,您就可以将任何数据写入输出流(并再次读回)。数组(或其他集合)可以用它们的长度作为前缀,就像我在这里所做的那样。或者,如果在末尾放置一个 TERMINUS 标记(有点像以空字符结尾的字符串),则可以避免编写长度前缀。

I alwaysuse 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.

当我实现自定义文件类型编解码器时,我总是使用这种设置,文件 IO 方法委托给流 IO 方法。通常,我后来发现我从这个流中读取和写入的对象可以很容易地写入一些更大和更复杂的文件。

So I might have a SuperFancyCodecfor 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.

所以我可能有一个SuperFancyCodec用于读取/写入整个系统的数据,它会调用我的TinySpecialPurposeCodec. 只要流读取和写入方法是公开的,那么我就可以使用面向组件的方法组装新的文件类型。

回答by aioobe

The extension usually have very little to do with how the file is interpreted.

扩展名通常与文件的解释方式关系不大。

If you'd like to have just config.datinstead of config.xmlyou just rename the file. (You'd typically give an xml-parser an InputStreamor a Readeras input, which may read any file, regardless of extension)

如果您只想重命名文件config.dat而不是config.xml重命名文件。(您通常将 xml 解析器 anInputStream或 aReader作为输入,它可以读取任何文件,而不管扩展名如何)

If the problem you're describing is about combiningmultiple 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.

如果您描述的问题是关于多个文件(.zip、.jar 等)合并到一个 .dat 文件中,您可以例如将它们压缩在一起,并使用 .dat 扩展名命名 zip 文件。Java 对 zip 文件有很好的支持,并且可以处理 zip 文件而不管文件名/扩展名。

回答by Jesse Webb

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.

在 Java(或其他任何东西)中创建/读取文件时,文件扩展名并未严格绑定到文件数据的实际结构。如果我愿意,我可以制作一个 XML 文件file.gweebz。操作系统和应用程序不知道如何处理它,但是一旦打开,很明显它是 XML。

That being said, it is often good to follow the conventions already established and usually .datfiles 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.

话虽如此,遵循已经建立的约定通常是好的,通常.dat文件是二进制格式的文件。您可以根据需要使用 .dat,但请注意,某些用户可能具有该文件类型的操作系统绑定,单击您的文件可能会导致其系统出现与预期不同的行为。

As for how to do it in Java. Grabbing a file handle in Java is easy...

至于如何在Java中做到这一点。在 Java 中获取文件句柄很容易...

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.

就这么简单,您可以随意命名。您将需要其他类来读写文件或进行压缩,但我假设您知道如何做到这一点。如果没有,这个网站会有答案。