2008-11-27 13 views
34

Al diseñar un formato de archivo para grabar datos binarios, ¿qué atributos cree que debería tener el formato? Hasta el momento, se me ha ocurrido con los siguientes puntos importantes:¿Cuáles son los puntos importantes al diseñar un formato de archivo (binario)?

  • tener algunos "bytes mágicos" al principio, para ser capaz de reconocer los archivos (en mi caso concreto, esto debe también contribuir a distinguir la archivos de archivos "heredados")
  • tienen un número de versión de archivo al principio, por lo que el formato de archivo se puede cambiar más tarde sin romper la compatibilidad
  • especifique la endianidad y el tamaño de todos los elementos de datos; o: incluya algún espacio para describir la endianidad/tamaño de los datos (me inclinaría por los primeros)
  • ¿podría reservar espacio para otros atributos por archivo que puedan ser necesarios en el futuro?

¿Qué otra cosa sería útil para que el formato sea más a prueba de futuro y minimice el dolor de cabeza en el futuro?

Respuesta

22

Eche un vistazo a PNG spec. Este formato tiene algunos muy buenos fundamentos detrás de esto.

Además, decida qué es importante para su futuro formato: compacidad, compatibilidad, lo que permite incrustar otros formatos (diferentes algoritmos de compresión) dentro de él. Otro ejemplo interesante sería el Google's protocol buffers, donde el tamaño de los datos transferidos es el rey.

En cuanto a endianness, le sugiero que elija una opción y se quede con ella, no permitiendo órdenes de bytes diferentes. De lo contrario, las bibliotecas de lectura y escritura solo se volverán más complejas y lentas.

+0

PNG es un ejemplo. Otros con una estructura similar son IFF (formato de archivo de intercambio, utilizado principalmente en el Commodore Amiga) y RIFF (por ejemplo, WAV o AVI). – BlaM

3

Consideraría definir una subestructura que los niveles más altos usan para almacenar datos, un poco como un mini sistema de archivos dentro del archivo.

Por ejemplo, aunque su formato de archivo va a almacenar datos específicos de la aplicación, consideraría la definición de registros/secuencias, etc. dentro del archivo de tal manera que el código independiente de la aplicación pueda comprender el diseño del archivo, pero por supuesto no entiende las cargas útiles opacas.

Seamos un poco más concretos. Tenga en cuenta las formas habituales de almacenar datos en la memoria: generalmente se pueden resumir en matrices/listas expansibles contiguas, gráficos basados ​​en punteros/referencias y blobs binarios de datos en formatos particulares.

Por lo tanto, puede ser útil definir el formato de archivo binario en líneas similares. Utilice encabezados de registro que indiquen la longitud y la composición de los siguientes datos, ya sea en forma de matriz (una lista de registros de tipo idéntico), referencias (desplazamientos a otros registros en el archivo) o blobs de datos (por ejemplo, datos de cadena) en una codificación particular, pero que no contiene ninguna referencia).

Si se diseña cuidadosamente, esto puede permitir que el formato de archivo se use no solo para ingresar y sacar datos de una sola vez, sino de manera incremental según sea necesario. Si la subestructura está diseñada correctamente, puede ser independiente de la aplicación y aún así permitir, p. una aplicación de recopilación de basura que se escribirá, que comprende los blobs, matrices y tipos de registro de referencia, y es capaz de rastrear a través del archivo y eliminar registros no utilizados (es decir, registros que ya no se apuntan).

Eso es solo una idea. Otros lugares para buscar ideas son, en general, diseños de sistemas de archivos o estrategias de almacenamiento físico de bases de datos relacionales.

Por supuesto, dependiendo de sus requisitos, esto puede ser excesivo. Es posible que simplemente busque un formato binario para la persistencia de datos en memoria, en cuyo caso, un enfoque a considerar son los registros etiquetados.

En este enfoque, cada parte de los datos tiene como prefijo una etiqueta. La etiqueta indica el tipo de datos que se siguen inmediatamente, y posiblemente su longitud y nombre. Las listas pueden tener el sufijo de una etiqueta de "final de lista" que no tiene carga útil. La etiqueta puede tener un identificador incrustado, por lo que las etiquetas que no se comprenden pueden ser ignoradas por el mecanismo de serialización cuando se leen cosas. A este respecto, se asemeja un poco a XML, excepto el uso de modismos binarios.

En realidad, XML es un buen lugar para buscar la longevidad a largo plazo de un formato de archivo. Mire sus capacidades de espacio de nombres. Si construye cuidadosamente su código de lectura y escritura, debería ser posible escribir aplicaciones que conservan la ubicación y el contenido de los datos etiquetados (recursivamente) que no entienden, posiblemente porque han sido escritos por una versión posterior de la misma aplicación.

2

Una forma de garantizar el futuro del archivo sería proporcionar bloques. Directamente después de los datos de su encabezado de archivo, puede comenzar el primer bloque. El bloque podría tener un código de bytes o palabra para el tipo de bloque, luego un tamaño en bytes. Ahora puede agregar arbitrariamente nuevos tipos de bloque y puede saltear hasta el final de un bloque.

11

Todo depende del propósito del formato, por supuesto.

Un enfoque flexible es estructurar todo el archivo como trillizos TLV (Tag-Length-Value). Por ejemplo, hacer que su archivo comprized de registros, cada registro que comienza con una cabecera de 4 bytes:

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

En cuanto a la orden de bits, si almacena indicador de orden de bits en el archivo, todas las aplicaciones tendrán que soportar todos endianness formatos. Por otro lado, si especifica una endianidad particular para sus archivos, solo las aplicaciones en plataformas con endiannes que no coincidan tendrán que realizar un trabajo adicional, y se puede decidir en tiempo de compilación (usando la compilación condicional).

+0

TLV parece estar en todas partes, tanto en todos lados que no veo su punto en todas partes. – Cheery

2

Estoy de acuerdo con la sugerencia de atzz de utilizar un sistema de valor de longitud de etiqueta. Para compatibilidad futura, puede almacenar un conjunto de "punteros" a las entradas TLV al inicio (o tal vez Etiqueta, Puntero y hacer que el puntero apunte a Longitud, Valor o tal vez Etiqueta, Longitud, Puntero y luego tener todos los datos juntos ¿en otra parte?).

lo tanto, mi archivo podría ser algo así como:

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

Número mágico, versión, etiquetas, indicadores y longitudes todos serían una longitud conjunto predefinido, para facilitar la decodificación. Digamos, 2 bytes. O 4, dependiendo de lo que necesites. No todos necesitan ser iguales (por ejemplo, todas las etiquetas son de 1 byte, las punteros son 4, etc.).

La etiqueta le permite saber qué se está almacenando. El puntero a donde dice (ya sea un valor de desplazamiento o absoluta, en bytes), la longitudte dice qué tan grande es el de datos, y el valor es longitud bytes de datos de tipo etiqueta. Si utiliza un decodificador MyFileFormat v1 en un archivo MyFileFormat v2, los punteros le permiten omitir las secciones que el decodificador v1 no comprende. Si simplemente omite etiquetas no válidas, probablemente simplemente use TLV en lugar de TPLV.

yo sea mano el código algo así, o tal vez definir mi formato en el ASN.1 y generar un codec (yo trabajo en las telecomunicaciones, por lo ASN.1/TLV tiene sentido para mí: -D)

3

Asegúrese que reserve un código de etiqueta (o mejor aún reserve un bit en cada etiqueta) que especifique un bloque/fragmento eliminado/libre. Los bloques se pueden eliminar simplemente cambiando el código de etiqueta actual de un bloque al código de etiqueta borrado o estableciendo el bit eliminado de la etiqueta. De esta forma, no necesita reestructurar completamente su archivo al eliminar un bloque.

Al reservar un bit en la etiqueta, existe la opción de recuperar el bloque (si no modifica los datos del bloque).

Por seguridad, sin embargo, es posible que desee poner a cero los datos del bloque eliminado, en este caso utilizaría una etiqueta especial eliminada/libre.

Estoy de acuerdo con Stepan, que debe elegir endianess, pero también tendría un indicador de endianess en el archivo. Si usa un indicador de endianess, puede considerar usar uno de los UniCode Byte Order Marks también como un indicador de cualquier codificación de texto UniCode utilizada para cualquier bloque de texto. La BOM suele ser los primeros bytes de los archivos de texto UniCoded, por lo que si su lista de materiales es la primera entrada en su archivo puede haber un problema de alguna utilidad identificando su archivo como texto UniCode (no creo que esto sea un problema) . Trataría/reservaría la lista de materiales como una de sus etiquetas normales (utilizando la lista de materiales UTF16 si utiliza las etiquetas de 16 bits o la lista de materiales UTF32 si usa etiquetas de 32 bits) con un bloque/pieza de longitud 0.

Ver también http://en.wikipedia.org/wiki/File_format

15

Estoy de acuerdo que estas son buenas ideas:

  1. números mágicos al principio. Más o menos required en * nix:

  2. Número de versión del archivo para compatibilidad con versiones anteriores.

  3. Endianness specification.

Sin embargo, su cuarto es una exageración, porque # 2 le permite añadir campos, siempre y cuando se cambia el número de versión (y siempre y cuando no es necesario forward compatibility).

  • Posiblemente se reserve algo de espacio para otros atributos por archivo que puedan ser necesarios en el futuro?

Además, la idea de imponer una estructura de bloques en su archivo, expresada en muchas otras respuestas, parece menos un requisito universal para archivos binarios que una solución a un problema con ciertos tipos de cargas útiles.

Además de 1-3 anteriormente, me gustaría añadir los siguientes:

  • suma de comprobación simple o otra forma de detectar que los contenidos están intactos. De lo contrario, no puede confiar en los mágicos bytes o números de versión. Tenga cuidado de especificar qué bytes están incluidos en la suma de comprobación. Normalmente incluiría todos los bytes en el archivo que aún no tienen detección de errores.

  • versión de su software (incluido el número más granular que tiene, por ejemplo, el número de compilación) que escribió el archivo. Obtendrá un informe de error con un archivo adjunto de alguien que no puede abrirlo y no tendrán ninguna pista cuando escribieron el archivo porque el error no ocurrió en ese momento. Pero el error está en la versión que lo escribió, no en la que intenta leerlo.

  • Que sea claro en la especificación de que este es un formato binario , es decir, todos los valores 0-255 están permitidos para todos los bytes (excepto los números mágicos).

Y aquí están algunos opcionales:

  • Si necesita compatibilidad hacia adelante, necesita alguna manera de expresar lo que "trozos" son "opcionales" (como png hace), de manera que una versión anterior de su software puede pasar por alto con gracia.

  • Si espera que estos archivos se encuentren "en la naturaleza", puede considerar incluir alguna pista para encontrar la especificación. Imagine lo útil que sería encontrar la cadena http://www.w3.org/TR/PNG/ en un archivo png.

+0

¿Por qué el rango completo 0-255 es inaceptable para los números mágicos, como dijiste? Y, ¿cuál es el rango apropiado? Gracias por adelantado. – bazz

4

Otro punto, tomado de especificación de archivo .xz (http://tukaani.org/xz/xz-file-format.txt): uno de los primeros bytes debe ser un no-personaje "para evitar que aplicaciones de misdetecting el archivo como un archivo de texto.". Tenga en cuenta que cuántos bytes del encabezado suelen inspeccionar los editores y otras herramientas, pero utilizar un byte no binario en los primeros cuatro u ocho bytes parece útil.

1

Si se trata de datos de longitud variable, es mucho más eficiente usar punteros: Tener una matriz de punteros a los datos, a ser posible cerca del inicio del archivo, en lugar de almacenar los datos en una matriz directamente.

En este caso, es preferible la indicación porque permite el acceso aleatorio, que solo es posible si todos los elementos son del mismo tamaño. Si los datos se almacenaban directamente en una matriz, sin especificar la ubicación de ningún registro, el acceso a los datos tomaría O (n) en el peor de los casos; Para que su código de lectura de archivos acceda a un elemento en particular, debería conocer la longitud de todos los elementos anteriores, y la única forma de descubrirlo es mirar cada uno de ellos. Si está leyendo todo el archivo a la vez, entonces estaría haciendo esto de todos modos, por lo que no sería un problema. Pero si solo quieres una cosa, entonces este no es el camino a seguir.

Mientras que con una serie de punteros, es O (1) el tiempo todo: todo lo que necesita es un número de índice, y puede recuperar y seguir el puntero para obtener sus datos.

Al escribir un archivo con este método, por supuesto tendrá que crear su tabla en la memoria antes de escribir.

+0

Su respuesta me pareció interesante y esperaba poder elaborar un poco más. Actualmente estoy diseñando el formato bin y quería incorporar esta idea tuya. Si su tiempo lo permite, siéntase bienvenido a PM. –

2

Una de las cosas más importantes que debe saber antes de comenzar es cómo se usará su archivo.

  • ¿Será el acceso aleatorio o secuencial la norma?
  • ¿Con qué frecuencia se leerán los datos?
  • ¿Con qué frecuencia se escribirán los datos?
  • ¿Va a escribir el archivo de una vez o va a reducir la velocidad de escritura a medida que ingresan los datos.
  • ¿El archivo debe ser portátil? No todos los formatos deben serlo.
  • ¿Tiene que ser compatible con otras versiones? Quizás actualizar el archivo es suficiente.
  • ¿Tiene que ser fácil de leer/escribir?
  • Intercambio de tamaño/velocidad/Compexity.

La mayoría de las respuestas aquí dan buenos consejos sobre el frente de portabilidad/compatibilidad, así que no voy a agregar más. Pero considere las siguientes cosas (a menudo pasadas por alto).

  • Algunos archivos se escriben a menudo y rara vez leen (copias de seguridad, registros, ...) y es posible que desee centrarse en cuyo tamaño y fácil de escribir.
  • endianness la conversión es lento (relativamente) si el archivo no dejará el anfitrión, o sale rara vez es suficiente que la conversión es una buena opción que puede obtener un impulso significativo del rendimiento. Considere escribir un número como 0x1234 como parte del encabezado para que pueda detectar (e indicar al usuario que convierta) si este es el caso.
  • A veces la lectura fácil es realmente útil. Si está haciendo registros o documentos de texto, considere comprimir todo de una vez en lugar de por entrada para poder zcat | strings el archivo y ver qué hay dentro.

Hay muchas cosas a tener en cuenta y diseñar un buen formato requiere mucha planificación y previsión. Las pequeñas cosas como zcat en un archivo y obtener información útil o el pequeño aumento en el rendimiento del uso de enteros nativos pueden darle una ventaja a su producto, sin embargo, debe tener cuidado de no sacrificar algo importante para obtenerlo.

5

Solo para el registro, encontré this link, que podría estar relacionado con la pregunta anterior.

Cuestiones relacionadas