Il modo migliore per leggere i file binari strutturati con Java

Devo leggere un file binario in un formato precedente con Java.

In breve, il file ha un’intestazione composta da diversi numeri interi, byte e array di caratteri a lunghezza fissa, seguiti da un elenco di record che comprende anche numeri interi e caratteri.

In qualsiasi altra lingua struct s (C / C ++) o record s (Pascal / Delphi) che sono rappresentazioni byte per byte dell’intestazione e del record. Poi avrei letto i byte sizeof(header) in una variabile header e ho fatto lo stesso per i record.

Qualcosa del genere: (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 è il modo migliore per fare qualcosa di simile con Java? Devo leggere ogni singolo valore da solo o esiste un altro modo per fare questo tipo di “block-read”?

A mia conoscenza, Java ti costringe a leggere un file come byte piuttosto che essere in grado di bloccare la lettura. Se stavi serializzando oggetti Java, sarebbe una storia diversa.

Gli altri esempi mostrati utilizzano la class DataInputStream con un file, ma è anche ansible utilizzare una scorciatoia: la 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); 

Si noti che è ansible trasformare gli oggetti responce in una class, se ciò renderebbe più semplice.

Se dovessi usare Preon , tutto ciò che dovresti fare è questo:

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

Una volta che hai questo, crei Codec usando una singola linea:

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

E tu usi il Codec in questo modo:

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

È ansible utilizzare la class DataInputStream come segue:

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

Una volta ottenuti questi valori, puoi farlo con loro a tuo piacimento. Cerca la class java.io.DataInputStream nell’API per maggiori informazioni.

Potrei aver frainteso te, ma mi sembra che tu stia creando strutture in-memory che speri che siano una rappresentazione accurata byte-per-byte di ciò che vuoi leggere dal disco rigido, quindi copi tutta la roba nella memoria e manipolare di conseguenza?

Se è così, stai giocando un gioco molto pericoloso. Almeno in C, lo standard non impone cose come il riempimento o l’allineamento dei membri di una struttura. Per non parlare di cose come big / small endianness o bit di parità … Quindi, anche se il codice che si esegue è molto non portatile e rischioso – si dipende dal creatore del compilatore che non cambia idea sulle versioni future.

Meglio creare un automone per convalidare sia la struttura in lettura (byte per byte) da HD sia valida, e il riempimento di una struttura in memoria se è effettivamente OK. Potresti perdere alcuni millisecondi (non tanto quanto potrebbe sembrare che i moderni sistemi operativi facciano un sacco di cache di lettura del disco) anche se ottieni indipendenza dalla piattaforma e dal compilatore. Inoltre, il tuo codice verrà facilmente trasferito in un’altra lingua.

Post Edit: In un modo che condivido con te. Nei giorni positivi di DOS / Win3.11, una volta ho creato un programma C per leggere i file BMP. E usato esattamente la stessa tecnica. Tutto è stato bello fino a quando ho provato a compilarlo per Windows – ops !! Int era ora lungo 32 bit, anziché 16! Quando ho provato a compilare su Linux, ho scoperto che gcc aveva regole molto diverse per l’allocazione dei campi di bit rispetto a Microsoft C (6.0!). Ho dovuto ricorrere a trucchi macro per renderlo portatile …

Ho usato Javolution e javastruct, entrambi gestiscono la conversione tra byte e oggetti.

Javolution fornisce classi che rappresentano i tipi C. Tutto quello che devi fare è scrivere una class che descriva la struttura C. Ad esempio, dal file di intestazione C,

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

dovrebbe essere tradotto in:

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

Quindi chiama setByteBuffer per inizializzare l’object:

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

javastruct utilizza l’annotazione per definire i campi in una struttura C.

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

Per inizializzare un object:

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

Immagino che FileInputStream ti permetta di leggere in byte. Quindi, aprendo il file con FileInputStream e leggendo il sizeof (intestazione). Suppongo che l’intestazione abbia un formato e una dimensione fissi. Non vedo quello menzionato nel post iniziale, ma supponendo che sia il caso in quanto diventerebbe molto più complesso se l’intestazione avesse argomenti opzionali e dimensioni diverse.

Una volta che hai le informazioni, ci può essere una class di intestazione in cui assegni il contenuto del buffer che hai già letto. E quindi analizzare i record in modo simile.

Ecco un link per leggere il byte usando un ByteBuffer (Java NIO)

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

Come altre persone menzionano DataInputStream e Buffer sono probabilmente le API di basso livello che stai cercando per gestire i dati binari in java.

Comunque probabilmente vorrai qualcosa come Construct (la pagina wiki ha anche dei buoni esempi: http://en.wikipedia.org/wiki/Construct_(python_library) , ma per Java.

Non conosco nessuna versione (Java), ma assumere tale approccio (specificando dichiaratamente la struttura nel codice) sarebbe probabilmente la strada giusta da percorrere. Con un’interfaccia fluente adatta in Java probabilmente sarebbe abbastanza simile a una DSL.

EDIT: un po ‘di googling rivela questo:

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

Quale potrebbe essere il tipo di cosa che stai cercando. Non ho idea se funzioni o sia utile, ma sembra un buon punto di partenza.

Creo un object che avvolge una rappresentazione ByteBuffer dei dati e fornisce ai getter di leggere direttamente dal buffer. In questo modo, si evita di copiare i dati dal buffer ai tipi primitivi. Inoltre, è ansible utilizzare un MappedByteBuffer per ottenere il buffer di byte. Se i dati binari sono complessi, è ansible modellarli utilizzando le classi e assegnare a ciascuna class una versione a fette del 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); } } 

Sono utili anche i metodi per leggere i valori non firmati dai buffer di byte.

HTH

Ho scritto una tecnica per fare questo genere di cose in java – simile al vecchio idioma di tipo C della lettura dei campi di bit. Si noti che è solo un inizio, ma potrebbe essere ampliato.

Qui

In passato ho utilizzato DataInputStream per leggere i dati di tipi arbitrari in un ordine specificato. Ciò non ti consentirà di tenere facilmente conto dei problemi big-endian / little-endian.

A partire dal 1.4 la famiglia java.nio.Buffer potrebbe essere la strada da percorrere, ma sembra che il tuo codice potrebbe essere effettivamente più complicato. Queste classi hanno supporto per la gestione dei problemi di endian.

Qualche tempo fa ho trovato questo articolo sull’utilizzo di reflection e parsing per leggere dati binari. In questo caso, l’autore sta usando reflection per leggere i file java binary .class. Ma se stai leggendo i dati in un file di class, potrebbe essere di aiuto.