it-swarm.com.de

Der beste Weg, um strukturierte Binärdateien mit Java

Ich muss eine Binärdatei in einem Legacy-Format mit Java lesen.

Zusammenfassend hat die Datei einen Header, der aus mehreren Ganzzahlen, Bytes und Zeichenarrays fester Länge besteht, gefolgt von einer Liste von Datensätzen, die auch aus Ganzzahlen und Zeichen bestehen.

In jeder anderen Sprache würde ich structs (C/C++) oder records (Pascal/Delphi) erstellen, die byteweise Darstellungen des Headers und des Datensatzes sind. Dann würde ich sizeof(header) Bytes in eine Header-Variable einlesen und dasselbe für die Datensätze tun.

Etwas in der Art: (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;

Was ist der beste Weg, um etwas Ähnliches mit Java zu machen? Muss ich jeden einzelnen Wert einzeln lesen oder gibt es eine andere Möglichkeit, diese Art des "Blocklesens" durchzuführen?

49
Daniel Rikowski

Meines Wissens zwingt Java Sie, eine Datei als Byte zu lesen, anstatt das Lesen zu blockieren. Wenn Sie Java= Objekte serialisieren würden, wäre es eine andere Geschichte.

In den anderen gezeigten Beispielen wird die Klasse DataInputStream mit einer Datei verwendet. Sie können jedoch auch eine Verknüpfung verwenden: Die Klasse 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);

Beachten Sie, dass Sie die Antwortobjekte in eine Klasse verwandeln könnten, wenn dies die Arbeit erleichtern würde.

34
Powerlord

Wenn Sie Preon verwenden würden, müssten Sie nur Folgendes tun:

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

Sobald Sie dies haben, erstellen Sie Codec mit einer einzigen Zeile:

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

Und du verwendest den Codec so:

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

Sie können die DataInputStream-Klasse folgendermaßen verwenden:

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

etc.

Sobald Sie diese Werte erhalten haben, können Sie sie nach Belieben verwenden. Weitere Informationen finden Sie in der Java.io.DataInputStream-Klasse in der API.

19

Möglicherweise habe ich Sie missverstanden, aber es scheint, als würden Sie In-Memory-Strukturen erstellen, die hoffentlich byteweise genau darstellen, was Sie von der Festplatte lesen möchten. Kopieren Sie dann das gesamte Material in den Speicher und von dort manipulieren?

Wenn das tatsächlich der Fall ist, spielen Sie ein sehr gefährliches Spiel. Zumindest in C erzwingt der Standard keine Dinge wie das Auffüllen oder Ausrichten von Mitgliedern einer Struktur. Ganz zu schweigen von Dingen wie Big/Small Endianness oder Parity Bits ... Selbst wenn Ihr Code zufällig ausgeführt wird, ist er nicht portierbar und riskant - Sie sind davon abhängig, dass der Ersteller des Compilers seine Meinung bei zukünftigen Versionen nicht ändert.

Es ist besser, einen Automaten zu erstellen, um zu überprüfen, ob die von der Festplatte gelesene Struktur (Byte für Byte) gültig ist, und um eine In-Memory-Struktur zu füllen, wenn dies tatsächlich in Ordnung ist. Möglicherweise verlieren Sie einige Millisekunden (weniger, als es für moderne Betriebssysteme den Anschein hat, als würden sie viel Festplatten-Lese-Caching ausführen), obwohl Sie Plattform- und Compiler-Unabhängigkeit erlangen. Außerdem kann Ihr Code problemlos in eine andere Sprache portiert werden.

Post Edit: In gewisser Weise sympathisiere ich mit dir. In den guten alten Tagen von DOS/Win3.11 habe ich einmal ein C-Programm zum Lesen von BMP) -Dateien erstellt und genau dieselbe Technik verwendet. Alles war gut, bis ich versuchte, es zu kompilieren for Windows - oops !! Int war jetzt 32 Bits lang, anstatt 16! Als ich versuchte, unter Linux zu kompilieren, stellte ich fest, dass gcc für die Zuweisung von Bitfeldern ganz andere Regeln hatte als Microsoft C (6.0!). Ich musste auf Makrotricks zurückgreifen um es tragbar zu machen ...

10
Joe Pineda

Ich habe Javolution und Javastruct verwendet, beide übernehmen die Konvertierung zwischen Bytes und Objekten.

Javolution liefert Klassen, die C-Typen darstellen. Sie müssen lediglich eine Klasse schreiben, die die C-Struktur beschreibt. Zum Beispiel aus der C-Header-Datei,

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

sollte übersetzt werden 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();
}

Rufen Sie dann setByteBuffer auf, um das Objekt zu initialisieren:

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

javastruct verwendet Annotation, um Felder in einer C-Struktur zu definieren.

@StructClass
public class Foo{

    @StructField(order = 0)
    public byte b;

    @StructField(order = 1)
    public int i;
}

So initialisieren Sie ein Objekt:

Foo f2 = new Foo();
JavaStruct.unpack(f2, b);
7
Ko-Chih Wu

Hier ist ein Link zum Lesen eines Bytes mit einem ByteBuffer (Java NIO)

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

4
Javamann

Ich denke, mit FileInputStream können Sie in Bytes lesen. Öffnen Sie also die Datei mit FileInputStream und lesen Sie die sizeof (Header) ein. Ich gehe davon aus, dass der Header ein festes Format und eine feste Größe hat. Ich sehe das im ersten Beitrag nicht erwähnt, gehe aber davon aus, dass dies der Fall ist, da es viel komplexer werden würde, wenn der Header optionale Argumente und unterschiedliche Größen hat.

Sobald Sie die Informationen haben, kann es eine Header-Klasse geben, in der Sie den Inhalt des Puffers zuweisen, den Sie bereits gelesen haben. Und dann analysieren Sie die Datensätze auf ähnliche Weise.

4
Arvind

Wie andere Leute bereits erwähnt haben, sind DataInputStream und Buffers wahrscheinlich die APIs auf niedriger Ebene, die Sie für den Umgang mit Binärdaten in Java suchen.

Allerdings möchten Sie wahrscheinlich etwas wie Construct (Wiki-Seite hat auch gute Beispiele: http://en.wikipedia.org/wiki/Construct_ (python_library) , aber für Java.

Ich kenne keine (Java-Versionen) aus der Hand, aber diese Vorgehensweise (deklarativ die Struktur im Code anzugeben) wäre wahrscheinlich der richtige Weg. Mit einer geeigneten fließenden Schnittstelle in Java) wäre es wahrscheinlich einer DSL ziemlich ähnlich.

EDIT: ein bisschen googeln verrät dies:

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

Welches ist das, wonach Sie suchen? Ich habe keine Ahnung, ob es funktioniert oder gut ist, aber es sieht nach einem vernünftigen Ausgangspunkt aus.

3
John Montgomery

Ich würde ein Objekt erstellen, das eine ByteBuffer - Darstellung der Daten umschließt und Getter bereitstellt, die direkt aus dem Puffer gelesen werden können. Auf diese Weise vermeiden Sie das Kopieren von Daten aus dem Puffer in primitive Typen. Außerdem können Sie MappedByteBuffer verwenden, um den Byte-Puffer abzurufen. Wenn Ihre Binärdaten komplex sind, können Sie sie mithilfe von Klassen modellieren und jeder Klasse eine aufgeschnittene Version Ihres Puffers zuweisen.

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);
    }
}

Ebenfalls nützlich sind die Methoden zum Lesen vorzeichenloser Werte aus Byte-Puffern.

HTH

3
anonymous

Ich habe eine Technik geschrieben, um so etwas in Java - ähnlich der alten C-ähnlichen Sprache des Lesens von Bitfeldern zu tun. Beachten Sie, dass es nur ein Anfang ist, aber erweitert werden könnte .

hier

2
Steve Flanagan

Vor einiger Zeit fand ich diesen Artikel über die Verwendung von Reflection und Parsing zum Lesen von Binärdaten. In diesem Fall verwendet der Autor Reflection, um die Java binären .class-Dateien zu lesen. Wenn Sie die Daten jedoch in eine Klassendatei einlesen, kann dies hilfreich sein.

1

In der Vergangenheit habe ich DataInputStream verwendet, um Daten beliebigen Typs in einer bestimmten Reihenfolge zu lesen. Auf diese Weise können Sie Big-Endian-/Little-Endian-Probleme nicht einfach berücksichtigen.

Ab Version 1.4 ist möglicherweise die Java.nio.Buffer-Familie die richtige Wahl, aber es scheint, dass Ihr Code tatsächlich komplizierter ist. Diese Klassen unterstützen die Behandlung von Endian-Problemen.

1
Darron