使用 Java MappedByteBuffer 进行并发读取

Posted

技术标签:

【中文标题】使用 Java MappedByteBuffer 进行并发读取【英文标题】:Concurrent Reads with a Java MappedByteBuffer 【发布时间】:2017-10-23 07:07:53 【问题描述】:

我正在尝试使用 MappedByteBuffer 来允许多个线程同时读取文件,并具有以下约束:

文件太大,无法加载到内存中 线程必须能够异步读取(它是一个网络应用) 该文件永远不会被任何线程写入 每个线程将始终知道它需要读取的字节的确切偏移量和长度(即 - 应用程序本身不会“寻找”)。

根据文档 (https://docs.oracle.com/javase/8/docs/api/java/nio/Buffer.html),缓冲区不是线程安全的,因为它们保持内部状态(位置等)。有没有办法在不将文件全部加载到内存的情况下对文件进行并发随机访问?

虽然FileChannel 在技术上是线程安全的,但来自文档:

如果文件通道是从现有流或随机访问文件中获取的,那么文件通道的状态与 getChannel 方法返回通道的对象的状态密切相关。改变通道的位置,无论是显式地还是通过读取或写入字节,都会改变原始对象的文件位置,反之亦然

所以看起来它只是同步的。如果我要在每个线程中使用new RandomAccessFile().getChannel().map() [编辑:每次读取],那么这不会导致 MappedByteBuffers 应该避免的每次读取的 I/O 开销吗?

【问题讨论】:

ByteBuffer 上的“asReadOnlyBuffer”或“duplicate”函数怎么样?根据文档,这似乎是您要查找的内容,但不确定。 如果你能设法只使用索引get()而不是相对gets(),你可以在所有线程之间无害地共享MappedByteBuffer 【参考方案1】:

FileChannel 支持不同步的读取操作。它在 Linux 上原生使用 pread

public abstract int read(ByteBuffer dst, long position) throws IOException

这是FileChannel 文档:

...其他操作,特别是那些采取明确立场的操作,可以同时进行;他们是否真的这样做取决于底层实现,因此未指定。

返回读取了多少字节是非常原始的(见详情here)。但我认为你仍然可以利用它,假设“每个线程总是知道它需要读取的字节的确切偏移量和长度”

【讨论】:

【参考方案2】:

我不会使用多个线程进行并发读取,而是使用this approach(基于一个巨大的 CSV 文件的示例,该文件的行必须通过 HTTP 并发发送):

同时读取多个位置的单个文件不会让您走得更快(但它可能会大大减慢您的速度)。

不是从多个线程读取文件,而是从单个线程读取文件,并并行处理这些行的处理。单个线程应逐行读取您的 CSV,并将每一行放入队列中。然后,多个工作线程应该从队列中取出下一行,解析它,转换为请求,并根据需要并发处理请求。然后工作的拆分将由单个线程完成,确保没有丢失的行或重叠。

如果您可以逐行读取文件,则 LineIterator 来自 Commons IO 是一种节省内存的可能性。如果您必须使用块,您的 MappedByteBuffer 似乎是一种合理的方法。对于队列,我会使用具有固定容量的阻塞队列——例如ArrayBlockingQueue——来更好地控制内存使用(队列中的行/块 + 工作人员之间的行/块 = 内存中的行/块)。

【讨论】:

以上是关于使用 Java MappedByteBuffer 进行并发读取的主要内容,如果未能解决你的问题,请参考以下文章

Java MappedByteBuffer.isLoaded()

Java,为啥从 MappedByteBuffer 读取比从 BufferedReader 读取慢

java中使用MappedByteBuffer将 File类转ByteBuffer

Java NIO - MappedByteBuffer 的截断

Java NIO MappedByteBuffer OutOfMemoryException

神奇的MappedByteBuffer