使用Java读取结构化二进制文件的最佳方法

我必须用Java读取传统格式的二进制文件。

简而言之,这个文件有一个由多个整数,字节和固定长度的char数组组成的头文件,后面跟着一个由整数和字符组成的logging列表。

在任何其他语言中,我将创buildstruct s(C / C ++)或record s(Pascal / Delphi),它们是标头和logging的逐字节表示。 然后,我将sizeof(header)字节读入一个头部variables,并为logging做同样的事情。

像这样的东西:(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做类似的最好的方法是什么? 我是否必须自己阅读每一个价值,还是有其他的方式来做这种“块阅读”?

据我所知,Java迫使你读取一个文件作为字节,而不是能够阻止读取。 如果你正在序列化Java对象,这将是一个不同的故事。

显示的其他示例使用DataInputStream类与File,但也可以使用快捷方式: 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); 

请注意,您可以将响应对象转换为类,如果这样做会更容易。

您可以使用DataInputStream类,如下所示:

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

一旦你得到这些价值观,你可以随心所欲地做。 在API中查找java.io.DataInputStream类以获取更多信息。

如果你要使用Preon ,那么你只需要这样做:

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

一旦你有了这个,你使用一行创build编解码器:

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

你使用这样的编解码器:

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

我可能误解了你,但是在我看来,你正在创build内存结构,你希望能够以每字节为单位精确地表示你想从硬盘读取的内容,然后将整个内容复制到内存中,操纵?

如果确实如此,那么你正在玩一个非常危险的游戏。 至less在C语言中,这个标准并没有强制填充或者alignment一个结构体的成员。 更不用说大/小sorting或奇偶校验位了……所以,即使你的代码碰巧运行,它也是非常不便携和风险的 – 你依赖于编译器的创build者在未来的版本中不会改变主意。

最好创build一个自动机来validation从HD读取的结构(每个字节的字节)是否有效,如果确实没问题,则填充内存中的结构。 尽pipe你获得了平台和编译器的独立性,但是你可能会丢失一些毫秒(而不是现代操作系统看起来很多的磁盘读取caching)。 另外,你的代码将被轻松移植到另一种语言。

post编辑:以某种方式,我同情你。 在DOS / Win3.11的良好日子里,我曾经创build了一个C程序来读取BMP文件。 并使用完全相同的技术。 一切都很好,直到我试图编译它的Windows – 哎呀! Int现在是32位长,而不是16位! 当我尝试在Linux上编译时,发现gcc与Microsoft C(6.0!)有不同的位域分配规则。 我不得不诉诸macros伎俩,使其便携式…

我使用Javolution和javastruct,都处理字节和对象之间的转换。

Javolution提供了表示Ctypes的类。 所有你需要做的是编写一个描述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); 

我猜FileInputStream让你读取字节。 所以,用FileInputStream打开文件并读入sizeof(header)。 我假设头有一个固定的格式和大小。 我没有看到在初始文章中提到的,但假设情况是如此,如果头部具有可选的参数和不同的尺寸,将会变得更复杂。

一旦你有了信息,就可以有一个头文件类,你可以在其中分配你已经读过的缓冲区的内容。 然后以类似的方式parsinglogging。

这是一个使用ByteBuffer(Java NIO)读取字节的链接,

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

正如其他人所说,DataInputStream和Buffers可能是您在处理java中的二进制数据之后的低级API。

不过,你可能需要像构造 (维基页面也有很好的例子: http : //en.wikipedia.org/wiki/Construct_(python_library) ,但对于Java。

我不知道任何(Java版本),但采取这种方法(声明性地指定在代码中的结构)可能是正确的路要走。 在Java中使用合适的stream畅接口可能与DSL非常相似。

编辑:一点谷歌search显示:

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

这可能是你正在寻找的东西。 我不知道它是有效的还是好的,但它看起来是一个明智的开始。

我将创build一个包装数据的ByteBuffer表示的对象,并提供getter直接从缓冲区中读取。 这样,您可以避免将数据从缓冲区复制到原始types。 此外,您可以使用MappedByteBuffer来获取字节缓冲区。 如果你的二进制数据是复杂的,你可以使用类来build模,并为每个类分配一个缓冲区版本。

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

从字节缓冲区读取无符号值的方法也是有用的。

HTH

我已经写了一个技术来做这种事情在Java中 – 类似于旧的C类阅读位字段的成语。 请注意,这只是一个开始,但可以扩展。

这里

在过去,我使用DataInputStream以指定的顺序读取任意types的数据。 这不会让你轻易解释big-endian / little-endian的问题。

从1.4版本开始,java.nio.Buffer系列可能是要走的路,但是看起来你的代码实际上可能更复杂。 这些类确实支持处理endian问题。

前一段时间,我发现这篇文章使用reflection和parsing来读取二进制数据。 在这种情况下,作者使用reflection来读取java二进制.class文件。 但是,如果您正在将数据读入类文件,可能会有所帮助。