我正在使用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
答案 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.position
:keyIndex - 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 |