读取文件中最后一行的最快方法

问题描述:

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

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

因为我必须做很多在这个文件的访问,性能十分照顾,于是我找了其他的选择来读取文件将涉及的重要问题特定的线路并获得整条线路。

我看过FileChannelMappedByteBuffer是一个很好的选择来快速读取文件,但我没有看到任何解决方案,做我想做的。

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

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

编辑

文件我要读有遵循格式:关键\t

该指数是与该文件已经键的所有键和值一个HashMap是字节位置(Long)。

让我们假设我想进入该关键“富”行,那我一定设法值位置,就像这样:

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

如果我使用raf.readLine()回报率将是整个与钥匙“foo”一致。

但我不想使用RandomAccessFile进行这项工作,因为它太慢了。

这是我在斯卡拉现在做的方式:

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

是不是访问到不同的文件?如果没有,为什么你关闭文件访问?如果保持文件访问权限不变,则不必等待操作系统授予您读取权限。 – Tschallacka

+0

@Tschallacka我只是在所有阅读结束时才结束,这只是一个例子。但是我的问题在于读取文件的方式。 –

+0

您能否提供索引阅读的代码以及如何将其翻译为查找位置?由于您已经走上了一条良好的道路,您的索引查找可能会从一些优化中受益,但是如果没有完整的代码和示例数据,则很难提供帮助。 – Tschallacka

如果您已经有通过文件读取一次找到钥匙的指数,绝对速度最快的解决办法是读线并记住它们。如果由于某些原因(例如内存限制)不起作用,使用缓冲区确实是一个很好的选择。这是代码大纲:

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"))); 

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

线尺寸:获得行大小的最好方法是储存它,当你通过文件扫描,即使用Map[String, (Long, Int)],而不是现在使用的是什么index。如果不为你工作,你应该运行一些测试,以找出什么是快:

  • 只存储最大行的大小,然后搜索这个最大长度的字符串中的换行。在这种情况下,请注意您覆盖了在单元测试中访问文件的末尾。
  • ByteBuffer.get继续扫描,直至遇到\n。如果您有真正的Unicode文件,那么这可能不是一种选择,因为换行的ASCII码(0x0A)可以出现在其他地方,例如UTF-16编码的韩文音节中带有字符代码0xAC0A。

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

// 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"))) 
+0

我有一个索引,所以我可以访问一行的开头。我访问这个索引,然后在那里寻找。有了与RandomAccessFile不同的其他选项,我也想找到这个位置,索引也会被使用。 –

+0

我已经阅读过所有文件,然后创建了一个索引。我把这个索引放在内存中,这样我就可以访问它,并通过查找方法进入一行的开头。使用与RandomAccessFile不同的其他选项,我也想寻求这个位置,索引也会被使用。 –

+0

不,我不能把这个文件放在内存中,它大于100GB。我的解决方案有效,但速度很慢,这是我的问题。 –