Лучший способ читать структурированные двоичные файлы с помощью 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). Я предполагаю, что заголовок имеет фиксированный формат и размер. Я не вижу этого в первоначальном сообщении, но предполагаю, что это так, потому что он будет намного сложнее, если заголовок имеет необязательные аргументы и разные размеры.
После того, как у вас есть информация, может быть класс заголовка, в котором вы назначаете содержимое буфера, который вы уже прочитали. А затем проанализируйте записи аналогичным образом.
Ответ 7
Вот ссылка на чтение байта с помощью ByteBuffer (Java NIO)
http://exampledepot.com/egs/java.nio/ReadChannel.html
Ответ 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. Но если вы читаете данные в файл класса, это может помочь.