Подтвердить что ты не робот

Каковы важные моменты при разработке (двоичного) формата файла?

При разработке формата файла для записи двоичных данных какие атрибуты, по вашему мнению, должны иметь формат? До сих пор я придумал следующие важные моменты:

  • вначале имеют несколько "магических байтов", чтобы распознавать файлы (в моем конкретном случае это также должно помочь отличить файлы от "устаревших" файлов)
  • имеет номер версии файла в начале, так что формат файла можно изменить позже, не нарушая совместимость.
  • указать контенту и размер всех элементов данных; или: включить некоторое пространство для описания сущности/размера данных (я бы склонен к первому)
  • возможно зарезервировать некоторое пространство для дополнительных атрибутов каждого файла, которые могут потребоваться в будущем?

Что еще было бы полезно, чтобы сделать формат более перспективным и минимизировать головную боль в будущем?

4b9b3361

Ответ 1

Взгляните на спецификацию PNG. Этот формат имеет очень хорошее обоснование.

Также решайте, что важно для вашего будущего формата: компактность, совместимость, позволяющая внедрять в нее другие форматы (разные алгоритмы сжатия). Другим интересным примером может служить буферы протокола Google, где размер передаваемых данных является королем.

Что касается конкретизации, я бы предложил вам выбрать один вариант и придерживаться его, не позволяя разные байтовые заказы. В противном случае библиотеки чтения и записи будут только более сложными и медленными.

Ответ 2

Я согласен, что это хорошие идеи:

  • Магические числа в начале. В значительной степени требуется в * nix:

  • Номер версии файла для обратной совместимости.

  • Спецификация Endianness.

Но ваш четвертый слишком переполнен, потому что # 2 позволяет добавлять поля до тех пор, пока вы меняете номер версии (и до тех пор, пока вам не нужен форвардная совместимость).

  • возможно зарезервировать некоторое пространство для дополнительных атрибутов каждого файла, которые могут потребоваться в будущем?

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

В дополнение к 1-3 выше, я бы добавил:

  • простая контрольная сумма или другой способ обнаружения неповрежденности содержимого. В противном случае вы не можете доверять магическим байтам или номерам версий. Будьте осторожны, чтобы указать, какие байты включены в контрольную сумму. Обычно вы включаете все байты в файл, который еще не обнаруживает ошибки.

  • версия вашего программного обеспечения (включая самое подробное число, которое у вас есть, например номер сборки), который написал файл. Вы получите сообщение об ошибке с прикрепленным файлом от кого-то, кто не может его открыть, и они не будут иметь понятия, когда они напишут файл, потому что ошибка не возникла тогда. Но ошибка находится в версии, которая написала ее, а не в той, которая пытается ее прочитать.

  • В спецификации указывается, что это двоичный формат, т.е. все значения 0-255 разрешены для всех байтов (кроме магических чисел).

И вот некоторые дополнительные:

  • Если вам нужна передовая совместимость, вам нужно каким-то образом выразить, какие "куски" являются "необязательными" (например, png), так что предыдущая версия вашего программного обеспечения может пропустить их изящно.

  • Если вы ожидаете, что эти файлы будут найдены "в дикой природе", вы можете подумать о вложении некоторой подсказки, чтобы найти спецификацию. Представьте, насколько полезно было бы найти строку http://www.w3.org/TR/PNG/ в png файле.

Ответ 3

Конечно, все зависит от цели формата.

Один гибкий подход состоит в том, чтобы структурировать весь файл как триплеты TLV (Tag-Length-Value). Например, сделайте свой файл состоящим из записей, каждая запись начинается с 4-байтового заголовка:

1 byte  = record type
3 bytes = record length
followed by record content

Что касается контентоспособности, если вы сохраняете индикатор состояния в файле, все ваши приложения должны будут поддерживать все форматы endianness. С другой стороны, если вы укажете конкретную контенту для своих файлов, только приложения на платформах с несогласованными endiannes должны будут выполнить дополнительную работу, и ее можно решить во время компиляции (используя условную компиляцию).

Ответ 4

Только для записи я нашел эту ссылку, которая может быть связана с вопросом выше.

Ответ 5

Еще одна точка, взятая из спецификации .xz(http://tukaani.org/xz/xz-file-format.txt): один из первых байтов должен быть несимвольным, msgstr "чтобы приложения не ошибочно определяли файл как текстовый файл.". Обратите внимание, что количество заголовков заголовков обычно проверяется редакторами и другими инструментами, но использование не двоичного байта в первых четырех или восьми байтах кажется полезным.

Ответ 6

Я бы подумал об определении подструктуры, которую более высокие уровни используют для хранения данных, немного как мини файловая система внутри файла.

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

Давайте получим немного более конкретную. Рассмотрим обычные способы хранения данных в памяти: как правило, они могут быть сведены к смежным расширяемым массивам/спискам, графам с указателем/ссылкой и бинарным блокам данных в определенных форматах.

Таким образом, может быть полезно определить формат двоичного файла вдоль аналогичных строк. Используйте записи заголовков, которые указывают длину и состав следующих данных, будь то в виде массива (список идентично типизированных записей), ссылки (смещения на другие записи в файле) или капли данных (например, строковые данные в конкретной кодировке, но не содержащей ссылок).

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

Это только одна идея. Другие места для поиска идей - это, в общем, проекты файловой системы или стратегии физического хранения реляционных баз данных.

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

В этом подходе каждая часть данных имеет префикс с тегом. Тег указывает тип сразу следующих данных и, возможно, его длину и имя. Списки могут быть помечены тегом "конечного списка", который не имеет полезной нагрузки. Тег может иметь встроенный идентификатор, поэтому теги, которые не поняты, могут игнорироваться механизмом сериализации при чтении вещей. Это немного похоже на XML в этом отношении, за исключением использования двоичных идиом.

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

Ответ 7

Убедитесь, что вы зарезервировали код тега (или еще лучше зарезервировали бит в каждом теге), который указывает удаленный/свободный блок/кусок. Затем блоки можно удалить, просто изменив код тега текущего блока на удаленный тег или установив бит, удаленный тегом. Таким образом, вам не нужно сразу полностью реструктурировать свой файл при удалении блока.

Сохранение бит в теге обеспечивает возможность возможного удаления блока (если вы оставите данные блока без изменений).

Для безопасности, однако, вы можете захотеть обнулить данные удаленных блоков, в этом случае вы бы использовали специальный тег удаленный/бесплатный.

Я согласен с Степаном, что вы должны выбрать endianess, но у меня также будет индикатор endianess в файле. Если вы используете индикатор endianess, вы можете использовать один из UniCode Byte Order Marks также в качестве искателя любой кодировки UniCode, используемой для любых текстовых блоков. Спецификация обычно представляет собой первые несколько байтов текстовых файлов UniCoded, поэтому, если ваша спецификация является первой записью в вашем файле, может возникнуть проблема с некоторой полезностью, идентифицирующей ваш файл как текст UniCode (я не думаю, что это большая проблема), Я бы рассматривал/резервировал спецификацию в качестве одного из ваших обычных тегов (используя либо спецификацию UTF16, используя 16-битные теги, либо спецификацию UTF32 при использовании 32-битных тегов) с блоком/фрагментом длиной 0.

См. также http://en.wikipedia.org/wiki/File_format

Ответ 8

Один из способов будущего доказательства - предоставить блоки для блоков. Прямо после данных заголовка файла вы можете начать первый блок. Блок может иметь байтовый или текстовый код для типа блока, а затем размер в байтах. Теперь вы можете произвольно добавлять новые типы блоков, и вы можете пропустить до конца блока.

Ответ 9

Я согласен с предложением atzz использования системы определения длины тега. Для будущей совместимости вы могли бы сохранить набор "указателей" в записях TLV в начале (или, возможно, в теге, указателе и указателе указать длину, значение или, возможно, метку, длину, указатель, а затем собрать все данные вместе в другом месте?).

Итак, мой файл может выглядеть примерно так:

magic number/file id
version
tag for first data entry
pointer to first data entry --------+
tag for second data entry           |
pointer to second data entry        |
...                                 |
length of first data entry <--------+
value for first data entry
...

Магическое число, версия, теги, указатели и длины были бы предопределенной заданной длиной, что упростило бы декодирование. Скажем, 2 байта. Или 4, в зависимости от того, что вам нужно. Они не все должны быть одинаковыми (например, все теги - 1 байт, указатели - 4 и т.д.).

Тег позволяет узнать, что хранится. Указатель указывает вам, где (либо смещение, либо абсолютное значение, в байтах), длина указывает, насколько велики данные, а значение - байты длины данных тега типа. Если вы используете декодер MyFileFormat v1 в файле MyFileFormat v2, указатели позволяют пропустить разделы, которые декодер v1 не понимает. Если вы просто пропустите недопустимые теги, вы можете просто использовать TLV вместо TPLV.

Я бы либо передал код что-то вроде этого, либо, возможно, определил свой формат в ASN.1 и сгенерировал кодек (я работаю в телекоммуникациях, поэтому ASN.1/TLV имеет смысл для меня: -D)

Ответ 10

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

  • Будет ли случайный или последовательный доступ нормой?
  • Как часто будут считываться данные?
  • Как часто будут записываться данные?
  • Вы запишете файл за один раз или будете замедлять его запись по мере поступления данных.
  • Будет ли файл переносимым? Не все форматы должны быть.
  • Нужно ли быть совместимым с другими версиями? Возможно, обновление файла достаточно.
  • Нужно ли легко читать/писать?
  • Компрессия размера/скорости/компромиссности.

Большинство ответов здесь дают хорошее представление о переносимости/совместимости, поэтому я не собираюсь добавлять больше. Но рассмотрите следующие (часто забытые) вещи.

  • Некоторые файлы часто пишутся и редко читаются (резервные копии, журналы и т.д.), и вы можете сосредоточиться на создании файлов и простой записи.
  • Преобразование endianness медленное (относительно), если ваш файл никогда не покинет хост, или редко оставляет достаточно, чтобы преобразование было хорошим вариантом, вы можете получить значительное повышение производительности. Рассмотрите возможность записи числа, такого как 0x1234, как часть заголовка, чтобы вы могли обнаружить (и проинструктировать пользователя для преобразования), если это так.
  • Иногда простое чтение действительно полезно. Если вы делаете журналы или текстовые документы, подумайте о сжатии всего за один раз, а не за запись, чтобы вы могли zcat | strings сохранить файл и посмотреть, что внутри.

Есть много вещей, которые нужно иметь в виду, и разработка хорошего формата требует много планирования и предвидения. Маленькие вещи, такие как zcat с файлом и получение полезной информации или небольшой прирост производительности от использования собственных целых чисел, могут дать вашему продукту преимущество, однако вам нужно быть осторожным, чтобы вы не жертвовали чем-то важным, чтобы его получить.

Ответ 11

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

В этом случае предпочтение отдается предпочтению, поскольку оно допускает произвольный доступ, что возможно только в том случае, если все элементы имеют одинаковый размер. Если данные были непосредственно сохранены в массиве, без указания местоположения каких-либо записей, доступ к данным занял бы O (n) время в худшем случае; для того, чтобы ваш файл-считывающий код обращался к определенному элементу, ему нужно было знать длину всех предыдущих элементов, и единственный способ найти это - посмотреть на каждый из них. Если вы сразу читаете весь файл, тогда вы все равно это сделаете, так что это не проблема. Но если вы хотите только одно, тогда это не путь.

В то время как с массивом указателей это время O (1): все, что вам нужно, это индексный номер, и вы можете получить и следовать указателю, чтобы получить ваши данные.

При написании файла с использованием этого метода вам, разумеется, придется создавать таблицу в памяти перед записью.