Java中的内存映射文件

8

我一直在尝试编写一些非常快速的Java代码,需要进行大量的I/O操作。我使用了返回ByteBuffer的内存映射文件:

public static ByteBuffer byteBufferForFile(String fname){
    FileChannel vectorChannel;
    ByteBuffer vector;
    try {
        vectorChannel = new FileInputStream(fname).getChannel();
    } catch (FileNotFoundException e1) {
        e1.printStackTrace();
        return null;
    }
    try {
        vector = vectorChannel.map(MapMode.READ_ONLY,0,vectorChannel.size());
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
    return vector;
}

我遇到的问题是,ByteBuffer.array() 方法(应该返回一个 byte[] 数组)对只读文件无效。我想编写代码,使其既适用于在内存中构建的内存缓冲区,也适用于从磁盘读取的缓冲区。但我不想将所有缓冲区都包装在 ByteBuffer.wrap() 函数中,因为我担心这会减慢速度。所以我一直在编写两个版本的代码,一个使用 byte[],另一个使用 ByteBuffer
我应该只是包装所有东西吗?还是应该把所有东西都写两遍?

3
即使专家指导你应该朝一个方向前进,但这个方向可能不适合你的情况,所以请进行基准测试并发布结果。进行基准测试!!! - basszero
4个回答

10

是否有人实际检查了通过内存映射创建的ByteBuffers是否支持调用.array(),无论是只读还是读写?

据我所知,从我的探究来看,答案是否定的。通过MappedByteBuffer创建时,ByteBuffer.hbbyte[])始终被设置为null,因此ByteBuffer无法通过ByteBuffer.array()返回直接的byte[]数组。

这对我来说有点糟糕,因为我希望做类似于问题作者想要做的事情。


我同意。这很糟糕。我简直不敢相信 ByteBuffer 没有实现 array()。另一方面,我们进行了一些性能测试,发现有时使用内存映射文件的 .get() 比使用编程 I/O 更快,而有时使用编程 I/O 更快。这非常奇怪。但是编程 I/O 的差异比内存映射文件更大。 - vy32
4
一个byte[]必须在堆上。一个内存映射的内存块必须在堆之外。尽管区别最好是透明的,但我仍然更喜欢使用ByteBuffer的getLong/putLong方法(使用本地排序速度更快)。 - Peter Lawrey

7

不要重复造轮子是很好的。Apache提供了一个非常好用的库来执行I/O操作。请看http://commons.apache.org/io/description.html

它的应用场景是这样的。假设你有一些数据,你希望将其保留在内存中,但你事先不知道会有多少数据。如果数据太多,你想将其写入磁盘而不是占用内存,但你不想在必要之前写入磁盘,因为磁盘速度慢,需要跟踪清理资源。

所以你创建一个临时缓冲区并开始写入。如果/当你达到想要保留在内存中的阈值时,你需要创建一个文件,将缓冲区中的内容写入该文件,并将所有后续数据写入该文件而不是缓冲区。

这就是DeferredOutputStream为您完成的工作。它隐藏了在切换点上的所有混乱。你只需要在第一次创建延迟流时配置阈值,然后随心所欲地写入即可。

编辑:我刚刚使用谷歌进行了小型搜索,并找到了这个链接:http://lists.apple.com/archives/java-dev/2004/Apr/msg00086.html(闪电般的文件读写)。非常令人印象深刻。


如果我说错了,请纠正我。你正在寻找快速执行I/O操作的方法,对吗? - Gaurav Saini
实际上,我只是在寻找快速完成I的方法,但我也在寻找用最少的缓冲区复制处理缓冲区的方法。 - vy32
@GauravSaini:你是指Apache commons-io中的DeferredOutputStream吗?我在v2.3和v2.2的Javadoc中找不到这样的类。 - dma_k

4

将byte[]包装起来不会减慢速度……没有大量的数组复制或其他小的性能问题。根据JavaDocs:java.nio.ByteBuffer.wrap()

将一个byte数组包装成缓冲区。

新的缓冲区将由给定的字节数组支持;也就是说,对缓冲区的修改将导致数组被修改,反之亦然。新缓冲区的容量和限制将为array.length,其位置将为零,其标记将未定义。其支持数组将是给定的数组,其数组偏移量将为零。


谢谢。我只是担心需要使用.get(i)来读取每个字节,而不是[i],因为.get(i)涉及方法调用,而[i]是在字节码中完成的。 - vy32
4
这似乎是一个非常“精细”的性能问题,并且给我一种过早优化的感觉。JVM在这方面做得很好。对其进行基准测试以证明一切。 - Stu Thompson
实际上,我正在进行计算机取证,处理数千兆字节的信息。根据我的经验,迄今为止JVM的优化程度并不如我所希望的那样高。 - vy32
1
你有展示由于上述方法调用而导致性能下降的基准测试吗?我无法想象它会比较小,而且认为应该有其他更适合优化的领域。以科学的方法处理这些事情胜过一切。嗯...也许周末会出于好玩而调查一下! :) - Stu Thompson
1
另外,我必须问一下,您使用的Java版本是什么?每个主要版本都在JVM性能方面有了显著的改进。如果您的任务对性能非常敏感,那么您应该使用Java 6。 - Stu Thompson

1
使用ByteBuffer.wrap()功能不会带来太大的负担。它分配一个简单的对象并初始化一些整数。如果您需要使用只读文件,那么针对ByteBuffer编写算法是最好的选择。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接