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

标签 java nio randomaccessfile filechannel

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

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

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

我读到 FileChannelMappedByteBuffer 是快速读取文件的一个不错的选择,但我没有看到任何解决方案可以满足我的要求。

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

大家有什么好的解决办法吗?

编辑:

我要读取的文件具有以下格式:key\tvalue

索引是一个 HashMap ,其中该文件的所有键都是键,值是字节位置(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

最佳答案

如果您已经必须读取一次文件才能找到键的索引,那么绝对最快的解决方案是读取行并将它们保存在内存中。如果由于某种原因(例如内存限制)这不起作用,那么使用缓冲区确实是一个不错的选择。这是代码的概要:

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 查找 key 所在的“页面”:position = keyIndex/pageSize * pageSize,然后调用 buffer.position > 从该索引:keyIndex - 位置

如果您确实没有任何方法将对一个“页面”的访问权限分组在一起,那么您就不需要切片。性能不会那么好,但这可以让您进一步简化代码:

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 文件,这可能不是一个选择,因为换行符 (0x0A) 的 Ascii 代码可能出现在其他位置,例如在 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")))

关于java - 读取文件中一行的最快方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42376023/

相关文章:

java - 使用字节数组创建新字符串会产生奇怪的结果

java - 为gridBagLayout中的组件添加垂直距离

java - java中的窗口z排序

Java nio 连接正在创建多个套接字级连接,为什么?

java - 在 java 中寻找 "does everything buffer"- 决定使用 Netty

java - 将文件缓存在内存中并并行读取

从服务器接收数据时 FileOutputStream 出现 java.lang.ArrayIndexOutOfBoundsException

java - 重复函数的时间呈指数增加

java - Google App Engine 数据存储持久性太慢

Java NIO 选择器挂起 (jdk1.6_20)