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

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

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

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

Что-то вроде этого: (Дельфи)

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? Должен ли я читать каждое отдельное значение отдельно или есть ли другой способ сделать такое «блочное чтение»?


person Daniel Rikowski    schedule 10.11.2008    source источник


Ответы (12)


Насколько мне известно, 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);

Обратите внимание, что вы можете превратить объекты ответа в класс, если это упростит задачу.

person Powerlord    schedule 10.11.2008
comment
У этого ответа нет голосов (пока), но он содержит все, что я хотел сейчас. Спасибо. - person Daniel Rikowski; 10.11.2008
comment
Это также предполагает, что вы хотели превратить массив символов в строку, что может быть не так. - person Powerlord; 12.11.2008
comment
DataInputStream + BufferedInputStream может быть лучше, потому что RandomAccessFile выдает слишком много меньших запросов ввода-вывода к ОС. - person Dennis C; 13.01.2011

Если вы будете использовать 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);

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

Header header = Codecs.decode(codec, file);
person Wilfred Springer    schedule 12.08.2009
comment
Вы все еще поддерживаете Preon и его разработку? - person dwerner; 17.01.2014
comment
Я бы полностью поддержал его, если бы это помогло мне выплатить ипотеку. ;-) Рад обсудить варианты. - person Wilfred Springer; 17.01.2014
comment
Ссылка Preon мертва - person Derek; 18.02.2015

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

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

etc.

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

person Vincent Ramdhanie    schedule 10.11.2008
comment
Это то, чего я опасался, но, поскольку кто-то указал на проблемы переносимости с общим подходом к чтению целых структур/записей, я думаю, что это хорошо, что это невозможно сделать на Java. - person Daniel Rikowski; 10.11.2008
comment
Не могли бы вы объяснить мне, зачем заворачивать FileInputStream в BufferedInputStream? почему бы просто не использовать DataInputStream in = new DataInputStream(new FileInputStream("filename")); ? - person Tarik; 01.07.2015
comment
BufferedInputStream обеспечивает буферизацию потока. То есть он считывает больший кусок данных с диска и сохраняет его в буфере для доступа. Это приводит к меньшему чтению диска, следовательно, к большей эффективности. - person Vincent Ramdhanie; 01.07.2015

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

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

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

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

person Joe Pineda    schedule 10.11.2008
comment
Да, вы правы на 100%. Исходный файл создается приложением Delphi, и некоторые языковые функции помогают предотвратить распространенные проблемы. (Заполнение и выравнивание можно контролировать, например) Но я подумаю о переносимости... Спасибо. - person Daniel Rikowski; 10.11.2008
comment
Портативность всегда упоминается как проблема при использовании этого подхода. В C это быстро, как молния, и есть ситуации, когда эта производительность удобна, если не обязательна, даже если не гарантируется переносимость, это все равно может не быть проблемой (т.е. сценарии, в которых вы контролируете как программу, так и среды/платформы) . - person M.E.; 05.10.2020

Я использовал 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);
person Ko-Chih Wu    schedule 02.12.2011

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

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

person Arvind    schedule 10.11.2008

Вот ссылка для чтения байта с помощью ByteBuffer (Java NIO)

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

person Javamann    schedule 10.11.2008

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

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

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

РЕДАКТИРОВАТЬ: немного поиска в Google показывает это:

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

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

person John Montgomery    schedule 10.11.2008

Я бы создал объект, который обертывает 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);
    }
}

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

ХТН

person anonymous    schedule 04.03.2010

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

здесь

person Community    schedule 05.05.2009

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

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

person Darron    schedule 10.11.2008

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

person Thomas Jones-Low    schedule 10.11.2008