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

Dan*_*ski 49 java file

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

简而言之,该文件有一个标题,由几个整数,字节和固定长度的char数组组成,后跟一个记录列表,它们也包含整数和字符.

在任何其他语言中,我将创建structs(C/C++)或records(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;
Run Code Online (Sandbox Code Playgroud)

用Java做类似事情的最佳方法是什么?我是否必须自己阅读每一个值,还是有其他方法来做这种"块读"?

Pow*_*ord 34

据我所知,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);
Run Code Online (Sandbox Code Playgroud)

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

  • DataInputStream + BufferedInputStream可能会更好,因为RandomAccessFile会向OS发出太多小的io请求 (4认同)

Wil*_*ger 20

如果您要使用Preon,那么您所要做的就是:

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

完成后,您可以使用一行创建Codec:

Codec<Header> codec = Codecs.create(Header.class);
Run Code Online (Sandbox Code Playgroud)

你像这样使用Codec:

Header header = Codecs.decode(codec, file);
Run Code Online (Sandbox Code Playgroud)


Vin*_*nie 19

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

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

etc.
Run Code Online (Sandbox Code Playgroud)

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


Joe*_*eda 10

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

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

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

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


小智 7

我使用了Javolution和javastruct,它们都处理字节和对象之间的转换.

Javolution提供了表示C类型的类.您需要做的就是编写一个描述C结构的类.例如,从C头文件,

struct Date {
    unsigned short year;
    unsigned byte month;
    unsigned byte day;
};
Run Code Online (Sandbox Code Playgroud)

应该翻译成:

public static class Date extends Struct {
    public final Unsigned16 year = new Unsigned16();
    public final Unsigned8 month = new Unsigned8();
    public final Unsigned8 day   = new Unsigned8();
}
Run Code Online (Sandbox Code Playgroud)

然后调用setByteBuffer初始化对象:

Date date = new Date();
date.setByteBuffer(ByteBuffer.wrap(bytes), 0);
Run Code Online (Sandbox Code Playgroud)

javastruct使用注释来定义C结构中的字段.

@StructClass
public class Foo{

    @StructField(order = 0)
    public byte b;

    @StructField(order = 1)
    public int i;
}
Run Code Online (Sandbox Code Playgroud)

要初始化对象:

Foo f2 = new Foo();
JavaStruct.unpack(f2, b);
Run Code Online (Sandbox Code Playgroud)