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

时间:2008-11-10 14:11:12

标签: java file

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

简而言之,该文件有一个包含几个整数,字节和固定长度字符数组的标题,后跟一个记录列表,这些记录也包含整数和字符。

在任何其他语言中,我将创建struct s(C / C ++)或record s(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做类似事情的最佳方法是什么?我是否必须自己阅读每一个值,还是有其他方法可以做这种“块读”?

12 个答案:

答案 0 :(得分:34)

据我所知,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);

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

答案 1 :(得分:20)

如果您要使用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);

答案 2 :(得分:19)

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

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

etc.

获得这些价值后,您可以随意使用它们。在API中查找java.io.DataInputStream类以获取更多信息。

答案 3 :(得分:10)

我可能误解了你,但在我看来,你正在创建内存结构,你希望每个字节的字节精确表示你想要从硬盘读取的内容,然后复制整个内容记忆并操纵它?

如果情况确实如此,那么你正在玩一个非常危险的游戏。至少在C中,标准不强制执行诸如填充或对齐结构成员之类的事情。更不用说像大/小字节序或奇偶校验位......所以即使你的代码碰巧运行它是非常不可移植和冒险的 - 你依赖编译器的创建者不会改变它对未来版本的想法。

最好创建一个自动机,以验证从HD读取的结构(每字节字节数)是否有效,并填写内存结构(如果确实可以)。尽管你获得了平台和编译器的独立性,但你可能会失去一些毫秒(不像现代操作系统看起来那样做很多磁盘读缓存)。此外,您的代码可以轻松移植到另一种语言。

帖子编辑:在某种程度上我同情你。在DOS / Win3.11的好日子里,我曾经创建了一个C程序来读取BMP文件。并使用完全相同的技术。一切都很好,直到我尝试为Windows编译它 - 哎呀! Int现在是32位长,而不是16位!当我尝试在Linux上编译时,发现gcc的位字段分配规则与Microsoft C(6.0!)完全不同。我不得不求助于宏观技巧让它变得便携......

答案 4 :(得分:7)

我使用了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);

答案 5 :(得分:4)

我想FileInputStream允许你以字节为单位读取。因此,使用FileInputStream打开文件并读入sizeof(标头)。我假设标题具有固定的格式和大小。我没有在最初的帖子中看到这一点,但假设情况如此,如果标题具有可选的args和不同的大小,它会变得更加复杂。

获得信息后,可以有一个标题类,您可以在其中分配已经读取的缓冲区的内容。然后以类似的方式解析记录。

答案 6 :(得分:4)

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

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

答案 7 :(得分:3)

正如其他人提到DataInputStream而Buffers可能是您在java中处理二进制数据的低级API。

但是你可能想要像Construct这样的东西(维基页面也有很好的例子:http://en.wikipedia.org/wiki/Construct_(python_library),但对于Java来说。

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

编辑:谷歌搜索显示:

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

这可能是你正在寻找的那种东西。我不知道它是否有效或有任何好处,但它看起来是一个明智的起点。

答案 8 :(得分:3)

我会创建一个包围数据ByteBuffer表示的对象,并提供直接从缓冲区读取的getter。这样,您就可以避免将数据从缓冲区复制到基本类型。此外,您可以使用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);
    }
}

来自字节缓冲区的methods for reading unsigned values也很有用。

HTH

答案 9 :(得分:2)

我已经编写了一种在java中执行此类操作的技术 - 类似于读取位字段的旧C语言习惯。请注意,这只是一个开始,但可以扩展。

here

答案 10 :(得分:1)

过去我使用DataInputStream以指定的顺序读取任意类型的数据。这不允许您轻松解决big-endian / little-endian问题。

从1.4开始,java.nio.Buffer系列可能就是这样,但似乎你的代码实际上可能更复杂。这些类确实支持处理字节序问题。

答案 11 :(得分:1)

前段时间我发现this article使用反射和解析来读取二进制数据。在这种情况下,作者使用反射来读取java二进制.class文件。但是如果你正在将数据读入类文件中,它可能会有所帮助。