2

我有包含存档二进制消息的文件。一个小文件大约 600MB,包含近 9000 条消息。每条消息都以我知道的一个特定的四字节标志开头,它指示消息头的前四个字节(因此必须捕获)。消息头是所有消息的固定大小。消息头后跟一个大小在头中标识的有效负载。一旦我找到了特定消息头的开头,我就知道该头的末尾有多少字节,并且可以使用它来提取消息中我需要解析此存档文件并隔离每条消息以进行处理的字节数,确保我包含从四字节标志的第一个字节到指定消息长度结尾的所有字节。不同的消息之间有一些填充。

由于文件的大小,我不想(并且可能在所有情况下都不能)将文件作为单个数组使用。因此,我正在研究诸如 and 之类的RandomAccessFile东西FileInputStream。扫描文件以查找特定的字节序列,然后从该序列中的第一个字节开始通过已知长度获取每个字节,这似乎不是一项简单的任务。RandomAccessFile,尤其是read(byte[])seek()方法似乎可以让我实现解决方案。

给出一个想法,我当前的实现涉及一个名为的方法,该方法findFlag()RandomAccessFile. 它寻找那个位置并读取从那里开始的四个字节。如果找到标志,则返回startPos. 否则,它会递归调用自己,移动到startPos + 1并重复,直到找到标志。由于我知道作为数据消息的一部分读取的最后一个字节,因此我将开始在那里寻找:

file.seek(startPos);

byte[] possibleFlag = new byte[4];

file.read(possibleFlag, 0, possibleFlag.length);

if (Arrays.equals(ByteUtils.intToBytes(Message.FLAG), possibleFlag)) {
    return startPos;
}
else {
    return findFlag(startPos + 1);
}

我是否忽略了 Java(Java 6 或更早版本)或经过良好测试的外部库(例如 Apache 库或类似库)中的某些内容?如果没有,是否有更好的解决方案来处理 Java 中的二进制数据或任何特别适合我的问题的方法?

4

3 回答 3

2

使用 java.nio.channels.FileChannel 扫描文件,它使用较少的中间副本将文件映射到内存。 替代品基准

于 2012-06-05T14:58:24.557 回答
1

这整个方法似乎无效。你怎么知道魔法字节不会出现在其他地方?例如在有效载荷或填充中。我希望你考虑到这一点。

摆脱递归。Java 不进行尾调用消除。迭代版本应该更清晰,更快。

限制分配的数量。为文件中的每个字节分配两个数组是完全不可接受的。

如果您使用FileChannel. 您可以使用MappedByteBuffer.getInt(int)遍历文件并将其与Message.FLAG. 这只是一个简单的 for 循环。

于 2012-06-05T15:46:56.267 回答
0

在我看来,这非常低效。文件上最昂贵的操作是随机部分——来回移动内部指针。你对每一个字节都这样做。+4、-3、+4、-3 等...表演死亡华尔兹。只需向前移动,您就可以完美地做到这一点。开始只搜索签名的第一个字节而不是整个序列。如果匹配,则测试下一个字节。如果有任何失败,只需重新开始搜索第一个字节。连续 4 次成功意味着你有你的签名。一直以来,你只是继续前进。不惜一切代价避免寻找。

此外,除非您绝对不在乎您的处理需要多长时间,否则您不应该仅仅基于功能而关闭 FileChannel。引用的统计数据是每 100MB 分钟数,我可以支持这一观察。FileChannel 比具有小读取大小的 RandomAccessFile 快两个数量级 - 你需要最小的一个:)

虽然递归通常被认为是程序员无所畏惧的标志,但如果你向它提供数百 MB 不包含任何签名的数据,这种特殊用法很容易让你的 VM 崩溃。

于 2012-06-05T21:28:20.647 回答