Лучший способ читать структурированные двоичные файлы с помощью Java

Мне нужно прочитать двоичный файл в устаревшем формате с Java.

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

На любом другом языке я бы создал struct (C/С++) или record (Pascal/Delphi), которые являются байтовыми представлениями заголовка и записи. Затем я читал sizeof(header) байты в переменной заголовка и делал то же самое для записей.

Что-то вроде этого: (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;

Каков наилучший способ сделать что-то подобное с Java? Должен ли я читать каждое отдельное значение самостоятельно или есть ли какой-либо другой способ сделать это "блочное чтение"?

Ответ 1

Насколько я знаю, Java заставляет вас читать файл как байты, а не блокировать чтение. Если бы вы сериализовали объекты Java, это была бы другая история.

В других приведенных примерах используйте класс DataInputStream с файлом, но вы также можете использовать ярлык: 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);

Обратите внимание, что вы можете превратить объекты responce в класс, если это облегчит процесс.

Ответ 2

Вы можете использовать класс DataInputStream следующим образом:

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

etc.

Как только вы получите эти значения, вы можете сделать с ними, как вам будет угодно. Посмотрите класс java.io.DataInputStream в API для получения дополнительной информации.

Ответ 3

Если вы будете использовать Preon, то все, что вам нужно сделать, это следующее:

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

После этого вы создаете кодек, используя одну строку:

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

И вы используете Codec следующим образом:

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

Ответ 4

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

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

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

Post Edit: В какой-то мере я сочувствую вам. В добрые дни DOS/Win3.11 я однажды создал программу C для чтения BMP файлов. И использовал точно такую ​​же технику. Все было хорошо, пока я не попытался скомпилировать его для Windows - oops!! Int был теперь 32 бита длиной, а не 16! Когда я попытался скомпилировать Linux, обнаружено, что gcc имеет очень разные правила для распределения битовых полей, чем Microsoft C (6.0!). Мне пришлось прибегать к макро-трюкам, чтобы сделать его переносным...

Ответ 5

Я использовал Javolution и javastruct, и обрабатывает преобразование между байтами и объектами.

Javolution предоставляет классы, представляющие типы C. Все, что вам нужно сделать, это написать класс, описывающий структуру C. Например, из файла заголовка C

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

следует перевести на:

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

Затем вызовите setByteBuffer для инициализации объекта:

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

javastruct использует аннотацию для определения полей в структуре C.

@StructClass
public class Foo{

    @StructField(order = 0)
    public byte b;

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

Для инициализации объекта:

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

Ответ 6

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

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

Ответ 8

Как говорят другие люди, DataInputStream и Buffers, вероятно, являются низкоуровневым API, который вы используете для обработки двоичных данных в java.

Однако вы, вероятно, хотите что-то вроде Construct (на странице вики также есть хорошие примеры: http://en.wikipedia.org/wiki/Construct_(python_library), но для Java.

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

РЕДАКТИРОВАТЬ: бит googling показывает это:

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

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

Ответ 9

Я бы создал объект, который обтекает ByteBuffer представление данных и предоставляет геттеры для чтения непосредственно из буфера. Таким образом, вы избегаете копирования данных из буфера в примитивные типы. Кроме того, вы можете использовать MappedByteBuffer для получения байтового буфера. Если ваши двоичные данные сложны, вы можете смоделировать их с помощью классов и дать каждому классу нарезную версию вашего буфера.

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

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

НТН

Ответ 10

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

здесь

Ответ 11

В прошлом я использовал DataInputStream для чтения данных произвольных типов в указанном порядке. Это не позволит вам легко учитывать проблемы большого и малого бизнеса.

Начиная с версии 1.4, семейство java.nio.Buffer может быть способным, но, похоже, ваш код может быть более сложным. У этих классов есть поддержка для обработки конечных проблем.

Ответ 12

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