52

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

简而言之,该文件的标题由几个整数、字节和固定长度的字符数组组成,然后是一个记录列表,该列表也由整数和字符组成。

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

4

12 回答 12

36

据我所知,Java 强制您以字节的形式读取文件,而不是阻止读取。如果您要序列化 ​​Java 对象,那就另当别论了。

显示的其他示例使用带有 File的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);

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

于 2008-11-10T14:41:08.197 回答
20

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

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

一旦你有了这个,你就可以使用一行来创建 Codec:

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

你像这样使用编解码器:

Header header = Codecs.decode(codec, file);
于 2009-08-12T14:52:04.033 回答
19

您可以按如下方式使用 DataInputStream 类:

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

etc.

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

于 2008-11-10T14:31:32.380 回答
10

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

如果确实如此,那你就是在玩一场非常危险的游戏。至少在 C 中,该标准不强制执行诸如填充或对齐结构成员之类的事情。更不用说诸如大/小字节序或奇偶校验位之类的事情了……因此,即使您的代码碰巧运行它也是非常不可移植且有风险的——您依赖于编译器的创建者不会在未来的版本上改变主意。

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

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

于 2008-11-10T15:58:16.277 回答
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);
于 2011-12-02T08:34:04.337 回答
4

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

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

于 2008-11-10T14:18:20.630 回答
4

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

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

于 2008-11-10T16:10:24.273 回答
3

正如其他人提到的那样,DataInputStream 和 Buffers 可能是您在 Java 中处理二进制数据所追求的低级 API。

但是,您可能想要Construct之类的东西(wiki 页面也有很好的示例:http://en.wikipedia.org/wiki/Construct_(python_library),但适用于 Java。

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

编辑:一些谷歌搜索揭示了这一点:

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

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

于 2008-11-10T16:15:28.037 回答
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);
    }
}

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

高温高压

于 2010-03-04T11:52:42.870 回答
2

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

这里

于 2009-05-05T01:06:43.070 回答
1

过去我使用 DataInputStream 按指定顺序读取任意类型的数据。这将不允许您轻松解决大端/小端问题。

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

于 2008-11-10T14:32:33.550 回答
1

不久前,我发现了这篇关于使用反射和解析来读取二进制数据的文章。在这种情况下,作者使用反射来读取 java 二进制 .class 文件。但是,如果您正在将数据读入类文件,它可能会有所帮助。

于 2008-11-10T15:53:04.120 回答