Создание и чтение из пользовательского типа файла в Java

#java

#java

Вопрос:

Я создаю установщик, и есть некоторые файлы ресурсов (.xmls, .zip-файлы, файл .jar и т.д.), Которые необходимо прочитать во время установки, Но я хотел бы упаковать их в пользовательский файл (т. Е. файл .dat), чтобы при распространении пользователям не приходилось с ними слишком много возиться. Проблема в том, что установщик должен быть написан на Java, а я никогда раньше не делал ничего подобного ни на одном языке программирования. Возможно ли это вообще? Если да, то как я могу упаковать его таким образом, чтобы впоследствии мое Java-приложение могло его прочитать, и как я могу заставить свое Java-приложение его прочитать?

Ответ №1:

Существует множество вопросов, на которые вам нужно будет ответить для себя относительно требований этого типа файла. Нужно ли его сжимать? Зашифрован? Нужно ли поддерживать чтение с произвольным доступом, или чтение с потоком достаточно хорошее?

Я могу ошибаться, но я не думаю, что это то, что вы задаете в этом вопросе. Если я вас правильно понял, я думаю, вы спрашиваете «как мне читать и записывать данные произвольного файла?»

Итак, на этот вопрос я отвечу. Обновите свой вопрос, если это не совсем то, что вы ищете.

Пользовательские типы файлов могут быть легко реализованы с использованием классов DataInputStream и DataOutputStream. Это позволит вам читать и записывать примитивы (boolean, char, byte, int, long, float, double) в поток. Существует также несколько удобных методов для чтения и записи строк в кодировке UTF-8, байтовых массивов и нескольких других полезных функций.

Давайте начнем.

В качестве аргумента давайте представим, что все мои элементы данных представляют собой массивы байтов. И у каждого из них есть имя. Таким образом, мой тип файла может быть логически смоделирован как 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 amp; 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;

   }

}
  

Основная идея заключается в том, что вы можете записать любые данные в выходной поток (и прочитать их снова), если вы можете представить эти данные в виде некоторой комбинации примитивов. Массивы (или другие коллекции) могут иметь префикс с указанием их длины, как я сделал здесь. Или вы можете избежать записи префикса длины, если поставите в конце указатель TERMINUS (что-то вроде строк с нулевым завершением).

Я всегда использую такого рода настройки при реализации пользовательского кодека filetype, при этом методы ввода-вывода файлов делегируются потоковым методам ввода-вывода. Обычно позже я обнаруживаю, что объект, который я считываю и записываю из этого потока, может быть так же легко записан в какой-нибудь более крупный и сложный файл.

Таким образом, у меня может быть SuperFancyCodec для чтения / записи данных для всей моей системы, и это вызывает мой TinySpecialPurposeCodec . Пока методы чтения и записи потока являются общедоступными, я могу собирать новые типы файлов, используя методологию, ориентированную на компоненты.

Ответ №2:

Расширение обычно имеет очень мало общего с тем, как интерпретируется файл.

Если вы хотите иметь просто config.dat вместо config.xml , просто переименуйте файл. (Обычно вы предоставляете xml-анализатору InputStream или a Reader в качестве входных данных, который может считывать любой файл, независимо от расширения)

Если проблема, которую вы описываете, связана с объединением нескольких файлов (.zip, .jar и т.д.) В один файл .dat, вы могли бы, например, сжать их вместе и назвать zip-файл расширением .dat. Java имеет хорошую поддержку zip-файлов и может обрабатывать zip-файл независимо от имени файла / расширения.

Комментарии:

1. «Расширение обычно имеет очень мало общего с тем, как интерпретируется файл». Это все, что я знаю. Но хороший указатель о том, как их архивировать, переименовывать и использовать Java API для обработки этого. Я не эксперт по Java, но я изучу это, спасибо.

2. Я последовал вашим первоначальным мыслям, за исключением того, что я извлек содержимое файла .dat в системную временную папку и получил оттуда нужные мне файлы. Сработало чудесно, спасибо!

Ответ №3:

При создании / чтении файлов на Java (или любом другом языке) расширение файла не привязано строго к фактической структуре данных файла. Если бы я хотел, я мог бы создать XML-файлы file.gweebz . Операционные системы и приложения не будут знать, что с ним делать, но после открытия будет ясно, что это XML.

При этом часто полезно следовать уже установленным соглашениям, и обычно .dat файлы представляют собой файлы в двоичном формате. Вы можете использовать .dat для того, что вы хотите, но имейте в виду, что у некоторых пользователей могут быть привязки ОС к типу файла, и нажатие на ваш файл может вызвать поведение, отличное от ожидаемого в их системах.

Что касается того, как это сделать в Java. Получить дескриптор файла в Java легко…

 File myFile = new File("/dir/file.gweebz");
  

Это очень просто, и вы можете назвать это как угодно. Вам понадобятся другие классы для записи и чтения из файла или для выполнения сжатия, но я предполагаю, что вы знаете, как это сделать. Если нет, на этом сайте будет ответ.