Melhor maneira de ler arquivos binários estruturados com Java

Eu tenho que ler um arquivo binário em um formato legado com o Java.

Em poucas palavras, o arquivo tem um header que consiste em vários inteiros, bytes e matrizes de caracteres de comprimento fixo, seguido por uma lista de registros que também consistem em inteiros e caracteres.

Em qualquer outra linguagem eu criaria struct s (C / C ++) ou record s (Pascal / Delphi) que são representações byte-by-byte do header e do registro. Então eu leria sizeof(header) bytes em uma variável de header e faria o mesmo para os registros.

Algo como isto: (Delphi)

 type THeader = record Version: Integer; Type: Byte; BeginOfData: Integer; ID: array[0..15] of Char; end; ... procedure ReadData(S: TStream); var Header: THeader; begin S.ReadBuffer(Header, SizeOf(THeader)); ... end; 

Qual é a melhor maneira de fazer algo semelhante com o Java? Eu tenho que ler cada valor individualmente ou há alguma outra maneira de fazer esse tipo de “leitura de bloco”?

Pelo que sei, o Java força você a ler um arquivo como bytes, em vez de poder bloquear a leitura. Se você estivesse serializando objects Java, seria uma história diferente.

Os outros exemplos mostrados usam a class DataInputStream com um arquivo, mas você também pode usar um atalho: A class RandomAccessFile :

 RandomAccessFile in = new RandomAccessFile("filename", "r"); int version = in.readInt(); byte type = in.readByte(); int beginOfData = in.readInt(); byte[] tempId; in.read(tempId, 0, 16); String id = new String(tempId); 

Note que você poderia transformar os objects de resposta em uma class, se isso tornasse mais fácil.

Se você usasse o Preon , tudo o que você teria que fazer é isto:

 public class Header { @BoundNumber int version; @BoundNumber byte type; @BoundNumber int beginOfData; @BoundString(size="15") String id; } 

Depois de ter isso, você cria o Codec usando uma única linha:

 Codec
codec = Codecs.create(Header.class);

E você usa o Codec assim:

 Header header = Codecs.decode(codec, file); 

Você pode usar a class DataInputStream da seguinte maneira:

 DataInputStream in = new DataInputStream(new BufferedInputStream( new FileInputStream("filename"))); int x = in.readInt(); double y = in.readDouble(); etc. 

Depois de obter esses valores, você pode fazer o que quiser com você. Procure a class java.io.DataInputStream na API para obter mais informações.

Eu posso tê-lo entendido mal, mas parece-me que você está criando estruturas na memory que você espera que sejam uma representação precisa do byte por byte do que você quer ler no disco rígido, então copie todo o material para a memory e manipular daí?

Se esse é realmente o caso, você está jogando um jogo muito perigoso. Pelo menos em C, o padrão não impõe coisas como preenchimento ou alinhamento de membros de uma estrutura. Sem mencionar coisas como big / small endianness ou parity bits … Portanto, mesmo que o seu código rode, ele é muito não-portátil e arriscado – você depende do criador do compilador não mudar sua mente em versões futuras.

É melhor criar um autômato para validar que a estrutura que está sendo lida (byte por byte) do HD é válida e preencher uma estrutura na memory, se realmente estiver OK. Você pode perder alguns milissegundos (embora não pareça que os sistemas operacionais modernos fazem muito cache de leitura de disco), embora você ganhe independência de plataforma e compilador. Além disso, seu código será facilmente transferido para outro idioma.

Post Edit: De certa forma eu simpatizo com você. Nos bons e velhos dias do DOS / Win3.11, criei um programa em C para ler arquivos BMP. E usou exatamente a mesma técnica. Tudo foi legal até que eu tentei compilá-lo para o Windows – oops !! Int tinha agora 32 bits, em vez de 16! Quando tentei compilar no Linux, descobri que o gcc tinha regras muito diferentes para a alocação de campos de bits do que o Microsoft C (6.0!). Eu tive que recorrer a truques macro para torná-lo portátil …

Eu usei Javolution e javastruct, ambos manipulam a conversão entre bytes e objects.

O Javolution fornece classs que representam tipos C. Tudo o que você precisa fazer é escrever uma class que descreva a estrutura C. Por exemplo, no arquivo de header C,

 struct Date { unsigned short year; unsigned byte month; unsigned byte day; }; 

deve ser traduzido em:

 public static class Date extends Struct { public final Unsigned16 year = new Unsigned16(); public final Unsigned8 month = new Unsigned8(); public final Unsigned8 day = new Unsigned8(); } 

Em seguida, chame setByteBuffer para inicializar o object:

 Date date = new Date(); date.setByteBuffer(ByteBuffer.wrap(bytes), 0); 

O javastruct usa annotations para definir campos em uma estrutura C.

 @StructClass public class Foo{ @StructField(order = 0) public byte b; @StructField(order = 1) public int i; } 

Para inicializar um object:

 Foo f2 = new Foo(); JavaStruct.unpack(f2, b); 

Eu acho que FileInputStream permite que você leia em bytes. Então, abrindo o arquivo com FileInputStream e leia no sizeof (header). Estou assumindo que o header tem um formato e tamanho fixos. Eu não vejo isso mencionado no post inicial, mas assumindo que é o caso, pois ficaria muito mais complexo se o header tivesse argumentos opcionais e tamanhos diferentes.

Depois de obter as informações, pode haver uma class de header na qual você atribui o conteúdo do buffer que você já leu. Em seguida, analise os registros de maneira semelhante.

Aqui está um link para ler byte usando um ByteBuffer (Java NIO)

http://exampledepot.com/egs/java.nio/ReadChannel.html

Como outras pessoas mencionam DataInputStream e Buffers são provavelmente a API de baixo nível que você está procurando para lidar com dados binários em java.

No entanto, você provavelmente quer algo como Construct (a página wiki também tem bons exemplos: http://en.wikipedia.org/wiki/Construct_(python_library) , mas para Java.

Eu não sei de nenhum (versões de Java), mas tomar essa abordagem (declarando explicitamente a estrutura no código) provavelmente seria o caminho certo a seguir. Com uma interface fluente adequada em Java, provavelmente seria muito semelhante a uma DSL.

EDIT: pouco de googling revela isso:

http://javolution.org/api/javolution/io/Struct.html

Qual pode ser o tipo de coisa que você está procurando. Não faço ideia se funciona ou não, mas parece um bom lugar para começar.

Gostaria de criar um object que envolve uma representação ByteBuffer dos dados e fornecer getters para ler diretamente do buffer. Dessa forma, você evita copiar dados do buffer para tipos primitivos. Além disso, você poderia usar um MappedByteBuffer para obter o buffer de byte. Se seus dados binários são complexos, você pode modelá-lo usando classs e dar a cada class uma versão fatiada do seu buffer.

 class SomeHeader { private final ByteBuffer buf; SomeHeader( ByteBuffer fileBuffer){ // you may need to set limits accordingly before // fileBuffer.limit(...) this.buf = fileBuffer.slice(); // you may need to skip the sliced region // fileBuffer.position(endPos) } public short getVersion(){ return buf.getShort(POSITION_OF_VERSION_IN_BUFFER); } } 

Também são úteis os methods para ler valores não assinados de buffers de byte.

HTH

Eu escrevi uma técnica para fazer esse tipo de coisa em java – semelhante ao antigo idioma similar ao C de ler campos de bits. Note que é apenas um começo, mas poderia ser expandido.

Aqui

No passado, usei DataInputStream para ler dados de tipos arbitrários em uma ordem especificada. Isso não permitirá que você contabilize facilmente problemas big-endian / little-endian.

A partir de 1.4 a família java.nio.Buffer pode ser o caminho a percorrer, mas parece que o seu código pode ser mais complicado. Essas classs têm suporte para lidar com problemas endianos.

Um tempo atrás eu encontrei este artigo sobre o uso de reflection e análise para ler dados binários. Nesse caso, o autor está usando a reflection para ler os arquivos .class binários do Java. Mas se você está lendo os dados em um arquivo de class, pode ser de alguma ajuda.