使用 Java MappedByteBuffer 进行并发读取



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

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

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

尽管FileChannel在技术上是线程安全的,但从文档中:

如果文件通道

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

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

与其使用多个线程进行并发读取,不如使用这种方法(基于一个包含巨大 CSV 文件的示例,该文件的行必须通过 HTTP 并发发送):

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

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

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

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

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

以下是FileChannel文档:

。其他行动,特别是采取明确立场的行动,可以同时进行。他们是否真的这样做取决于基础实现,因此没有指定。

通过返回读取的字节数,这是非常原始的(请参阅此处的详细信息)。但我认为您仍然可以使用它,假设"每个线程将始终知道它需要读取的确切偏移量和字节长度">

最新更新