读取文件中行的最快方法

时间:2017-02-21 19:15:45

标签: java nio randomaccessfile filechannel

我正在使用RandomAccessFile从大文件中读取一些信息。 RandomAccessFile有一个方法seek,它将光标指向我想要读取整行的文件的特定部分。要阅读此行,我使用readLine()方法。

之前我读过整个文件,然后创建了一个索引,允许我使用seek方法访问任何行的开头。这个指数运作正常。 我根据这个答案创建了这个索引:https://stackoverflow.com/a/42077860/763368

由于我必须在此文件中进行大量访问,因此性能是一个需要注意的重要问题,然后我正在寻找其他选项来读取特定行的文件并获取整行。

我读到FileChannel MappedByteBuffer是快速阅读文件的好选择,但我没有看到任何可以做我想要的解决方案。

P.S。:线条长度不同,我不知道这个长度。

有没有人有一个好的解决方案?

修改

我想要阅读的文件格式如下: \t

索引是一个散列映射,该文件的所有键都是键,值是字节位置(Long)。

假设我想要使用键“foo”进行操作,那么我必须寻找价值位置,如下所示:

raf.seek(index.get("foo"))

如果我使用raf.readLine(),则返回的内容将是“foo”键的整行。

但我不想将RandomAccessFile用于此项工作,因为它太慢了。

这就是我现在在Scala中所做的事情:

val raf = new RandomAccessFile(file,"r")  
raf.seek(position.get(key))
println(raf.readLine)
raf.close

1 个答案:

答案 0 :(得分:1)

如果您已经阅读过一次文件以查找键的索引,那么绝对最快的解决方案是读取行并将它们保存在内存中。如果由于某种原因(例如内存约束)不起作用,使用缓冲区确实可以是一个很好的选择。这是代码的概述:

FileChannel channel = new RandomAccessFile("/some/file", "r").getChannel();

long pageSize = ...; // e.g. "3 GB or file size": max(channel.size(), THREE_GB); 
long position = 0;
ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, position, pageSize);

ByteBuffer slice;
int maxLineLength = 30;
byte[] lineBuffer = new byte[maxLineLength];

// Read line at indices 20 - 25
buffer.position(20);
slice = buffer.slice();
slice.get(lineBuffer, 0, 6);
System.out.println("Starting at 20:" + new String(lineBuffer, Charset.forName("UTF8")));

// Read line at indices 0 - 10
buffer.position(0);
slice = buffer.slice();
slice.get(lineBuffer, 0, 11);
System.out.println("Starting at 0:" + new String(lineBuffer, Charset.forName("UTF8")));

此代码也可用于非常大的文件。只需致电channel.map,找到您的密钥所在的“页面”:position = keyIndex / pageSize * pageSize,然后从该索引中调用buffer.positionkeyIndex - position

如果您真的没有办法将访问权限分组到一个“页面”,那么您不需要slice。性能不会那么好,但这可以让您进一步简化代码:

byte[] lineBuffer = new byte[maxLineLength];
// ...
ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, keyIndex, lineLength);
buffer .get(lineBuffer, 0, lineLength);
System.out.println(new String(lineBuffer, Charset.forName("UTF8")));

请注意,{J}堆上未创建ByteBuffer,但实际上是OS级别的内存映射文件。 (从Java 8开始,您可以通过查看源代码并在实现中搜索sun.nio.ch.DirectBuffer来验证这一点。)

行大小:获取行大小的最佳方法是在扫描文件时存储它,即使用Map[String, (Long, Int)]而不是index使用的\n现在。如果这对您不起作用,您应该运行一些测试以找出更快的内容:

  • 只需存储最大行大小,然后在此最大长度的字符串中搜索换行符。在这种情况下,请注意您在单元测试中访问文件的末尾。
  • 使用ByteBuffer.get向前扫描,直至找到// this happens once val maxLineLength: Long = 2000 // find this in your initial sequential scan val lineBuffer = new Array[Byte](maxLineLength.asInstanceOf[Int]) // this is how you read a key val bufferLength = maxLineLength min (channel.size() - index("key")) val buffer = channel.map(FileChannel.MapMode.READ_ONLY, index("key"), bufferLength) var lineLength = 0 // or minLineLength while (buffer.get(lineLength) != '\n') { lineLength += 1 } buffer.get(lineBuffer, 0, lineLength - 1) println(new String(lineBuffer, Charset.forName("UTF8"))) 。如果你有真正的Unicode文件,这可能不是一个选项,因为换行符(0x0A)的Ascii代码可以出现在其他地方,例如在UTF-16编码的韩语音节中,字符代码为0xAC0A。

这将是第二种方法的Scala代码:

|                        | terms w.o. | terms with |
|                        | agg filter | agg filter |
|------------------------|-----------:|-----------:|
| total documents        | 41,690,696 | 41,690,696 |
| nested object          | 77,999,324 | 77,999,324 |
| filtered nested object | 77,999,324 |     89,116 |
| agg "ITEM 1"           |     87,905 |     89,116 |
| reversed agg           |     19,976 |     20,499 |