Best way to read structured binary files with Java
我必须使用Java读取旧格式的二进制文件。
简而言之,该文件的标头由几个整数,字节和固定长度的char数组组成,后跟一系列由整数和char组成的记录列表。
用任何其他语言,我都会创建
像这样:(Delphi)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
用Java做类似的事情的最好方法是什么? 我是否必须自己读取每个值,或者是否有其他方法可以进行这种"块读取"?
据我所知,Java强迫您以字节为单位读取文件,而不是阻止读取。如果要序列化Java对象,那就大不一样了。
显示的其他示例将DataInputStream类与File一起使用,但您也可以使用快捷方式:RandomAccessFile类:
1 2 3 4 5 6 7 | 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); |
请注意,您可以将响应对象变成一个类,如果那样的话会更容易。
如果您要使用Preon,那么您要做的就是:
1 2 3 4 5 6 | public class Header { @BoundNumber int version; @BoundNumber byte type; @BoundNumber int beginOfData; @BoundString(size="15") String id; } |
一旦有了这个,就可以使用一行创建编解码器:
1 | Codec<Header> codec = Codecs.create(Header.class); |
您可以像这样使用编解码器:
1 | Header header = Codecs.decode(codec, file); |
您可以如下使用DataInputStream类:
1 2 3 4 5 6 | DataInputStream in = new DataInputStream(new BufferedInputStream( new FileInputStream("filename"))); int x = in.readInt(); double y = in.readDouble(); etc. |
一旦获得这些值,就可以随意使用它们。在API中查找java.io.DataInputStream类以获取更多信息。
我可能误会了您,但是在我看来,您正在创建内存结构,希望该结构可以逐字节精确地表示您要从硬盘读取的内容,然后将整个内容复制到内存中并从那里操纵?
如果确实如此,那么您正在玩非常危险的游戏。至少在C语言中,该标准不会强制执行诸如填充或对齐结构成员之类的操作。更不用说诸如大/小尾数或奇偶校验位之类的东西了……因此,即使您的代码碰巧运行了,它也是非常不可移植且具有风险的-您依赖于编译器的创建者在未来版本上不会改变主意。
最好创建一个自动机,以验证从HD读取的结构(字节/字节)是否有效,如果确实可以,则填充内存结构。尽管您获得了平台和编译器的独立性,但您可能要花几毫秒的时间(不如现代操作系统执行大量磁盘读取缓存的时间少)。另外,您的代码将轻松移植到另一种语言。
编辑后:我以某种方式同情你。在DOS / Win3.11时代,我曾经创建一个C程序来读取BMP文件。并使用了完全相同的技术。一切都很好,直到我尝试为Windows编译它-糟糕!整数现在是32位长,而不是16位!当我尝试在Linux上进行编译时,发现的gcc与Microsoft C(6.0!)相比,位字段分配规则非常不同。我不得不借助宏技巧来使其可移植...
我使用了Javolution和javastruct,它们都处理字节和对象之间的转换。
Javolution提供了代表C类型的类。您所需要做的就是编写一个描述C结构的类。例如,从C头文件中,
1 2 3 4 5 |
应该翻译成:
1 2 3 4 5 |
然后调用
javastruct使用注释来定义C结构中的字段。
1 2 3 4 5 6 7 8 9 | @StructClass public class Foo{ @StructField(order = 0) public byte b; @StructField(order = 1) public int i; } |
初始化对象:
1 2 | Foo f2 = new Foo(); JavaStruct.unpack(f2, b); |
这是使用ByteBuffer(Java NIO)读取字节的链接
http://exampledepot.com/egs/java.nio/ReadChannel.html
我想FileInputStream可以读取字节数。因此,使用FileInputStream打开文件并读入sizeof(header)。我假设标题具有固定的格式和大小。我没有看到在最初的文章中提到的内容,但是假设是这种情况,因为如果标头具有可选的args和不同的大小,它将变得更加复杂。
获得信息后,可以有一个标头类,在其中分配已经读取的缓冲区的内容。然后以类似的方式解析记录。
我将创建一个对象,该对象包装数据的ByteBuffer表示形式并提供直接从缓冲区读取的吸气剂。这样,可以避免将数据从缓冲区复制到基本类型。此外,您可以使用MappedByteBuffer来获取字节缓冲区。如果二进制数据很复杂,则可以使用类对其进行建模,并为每个类提供缓冲区的切片版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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); } } |
从字节缓冲区读取无符号值的方法也很有用。
高温超导
正如其他人提到的那样,DataInputStream和Buffers可能是您在Java中处理二进制数据所追求的低级API。
但是,您可能想要类似Construct的东西(维基页面上也有很好的示例:http://en.wikipedia.org/wiki/Construct_(python_library),但是对于Java。
我暂时还不知道任何Java版本,但是采用这种方法(在代码中声明性地指定结构)可能是正确的方法。使用Java中合适的流利接口,它可能与DSL非常相似。
编辑:谷歌搜索揭示了这一点:
http://javolution.org/api/javolution/io/Struct.html
您可能正在寻找哪种东西。我不知道它是否有效或有什么好处,但它似乎是一个明智的起点。
我已经写了一种在Java中做这种事情的技术-类似于读取位域的古老C类习惯用法。请注意,这只是一个开始,但可以扩展。
这里
前一阵子,我发现这篇文章是关于使用反射和解析来读取二进制数据的。在这种情况下,作者正在使用反射读取Java二进制.class文件。但是,如果您将数据读取到类文件中,则可能会有帮助。
过去,我使用DataInputStream按指定顺序读取任意类型的数据。这将使您无法轻松解决大端/小端问题。
从1.4版本开始,也许可以使用java.nio.Buffer系列,但是看来您的代码实际上可能更复杂。这些类确实支持处理字节序问题。