我必须使用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做类似事情的最佳方法是什么?我是否必须自己阅读每一个值,还是有其他方法可以做这种“块读”?
答案 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)读取字节的链接
答案 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语言习惯。请注意,这只是一个开始,但可以扩展。
答案 10 :(得分:1)
过去我使用DataInputStream以指定的顺序读取任意类型的数据。这不允许您轻松解决big-endian / little-endian问题。
从1.4开始,java.nio.Buffer系列可能就是这样,但似乎你的代码实际上可能更复杂。这些类确实支持处理字节序问题。
答案 11 :(得分:1)
前段时间我发现this article使用反射和解析来读取二进制数据。在这种情况下,作者使用反射来读取java二进制.class文件。但是如果你正在将数据读入类文件中,它可能会有所帮助。