【问题标题】:Best way to read structured binary files with Java使用 Java 读取结构化二进制文件的最佳方法
【发布时间】:2008-11-10 14:11:12
【问题描述】:

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

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

在任何其他语言中,我会创建 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;

用 Java 做类似事情的最佳方法是什么?我必须自己读取每个值还是有其他方法可以进行这种“块读取”?

【问题讨论】:

    标签: java file binaryfiles


    【解决方案1】:

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

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

    【讨论】:

    • 这个答案没有任何投票(还),但它包含了我现在想要的所有内容。谢谢。
    • 这里也假设你想把char数组转成String,可能不是这样的。
    • DataInputStream+BufferedInputStream 可能会更好,因为 RandomAccessFile 确实向操作系统发出了太多较小的 io 请求
    【解决方案2】:

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

    【讨论】:

    • 你还在支持preon及其开发吗?
    • 如果它可以帮助我偿还抵押贷款,我会完全支持它。 ;-) 很高兴讨论这些选项。
    • Preon 链接已失效
    【解决方案3】:

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

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

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

    【讨论】:

    • 这就是我所担心的,但由于有人指出读取整个结构/记录的一般方法存在可移植性问题,我认为这是一个很好的想法,它不能在 Java 中完成。
    • 您能告诉我为什么将FileInputStream 包装在BufferedInputStream 中吗?为什么不直接使用DataInputStream in = new DataInputStream(new FileInputStream("filename"));
    • BufferedInputStream 为流提供缓冲。也就是说,它从磁盘读取更大的数据块并将其存储在缓冲区中以供访问。这样可以减少磁盘读取,从而提高效率。
    【解决方案4】:

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

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

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

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

    【讨论】:

    • 是的,你是 100% 正确的。原始文件由 Delphi 应用程序创建,并且有一些语言特性有助于防止常见问题。 (例如可以控制填充和对齐)但我会考虑便携性......谢谢。
    • 在尝试这种方法时,便携性总是被提及为一个问题。在 C 语言中,这快如闪电,并且在某些情况下,即使不是强制性的,这种性能也很方便,即使不能保证可移植性,这可能仍然不是问题(即,您同时控制程序和环境/平台的场景) .
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

        【解决方案7】:

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

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

        【讨论】:

          【解决方案8】:

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

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

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

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

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

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

          【讨论】:

            【解决方案9】:

            我将创建一个对象,该对象包含数据的 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

            【讨论】:

              【解决方案10】:

              我已经编写了一种在 java 中执行此类操作的技术 - 类似于读取位域的老式 C 类习惯用法。请注意,这只是一个开始,但可以继续扩展。

              here

              【讨论】:

                【解决方案11】:

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

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

                【讨论】:

                  【解决方案12】:

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

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-09-11
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多