读取文件中一行的最快方法
Fastest way to read a line in file
我正在使用 RandomAccessFile
从一个大文件中读取一些信息。
RandomAccessFile
有一个方法 seek
将光标指向我想要读取整行的文件的特定部分。要阅读这一行,我使用 readLine()
方法。
我之前阅读了整个文件,然后创建了一个索引,允许我使用 seek
方法访问任何行的开头。该索引工作正常。
我根据这个答案创建了这个索引:
因为我必须在这个文件中做很多访问,性能是一个需要注意的重要问题,然后我正在寻找其他选项来读取文件到特定行并获取整行。
我读到 FileChannel
和 MappedByteBuffer
是快速读取文件的好选择,但我没有看到任何解决方案满足我的要求。
P.S.: 线有不同的长度,我不知道这个长度。
有没有人有好的解决办法?
编辑:
我要读取的文件格式如下:key\t
value
索引是一个 hashmap,该文件的所有键都是键,值是字节位置 (Long
)。
假设我想转到带有键 "foo" 的行,那么我必须查找值位置,如下所示:
raf.seek(index.get("foo"))
如果我使用 raf.readLine()
,return 将是带有键 "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
即可找到您的密钥所在的 "page":position = keyIndex / pageSize * pageSize
,然后从该索引调用 buffer.position
:keyIndex - position
如果您真的没有办法将对一个 "page" 的访问分组在一起,那么您就不需要 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堆上创建的,实际上是OS级别的内存映射文件。 (从 Java 8 开始,您可以通过查看源代码并在实现中搜索 sun.nio.ch.DirectBuffer
来验证这一点)。
行大小: 获取行大小的最佳方法是在扫描文件时存储它,即使用 Map[String, (Long, Int)]
而不是您正在使用的内容现在 index
。如果这对您不起作用,您应该 运行 进行一些测试以找出更快的方法:
- 只需存储最大行大小,然后在这个最大长度的字符串中搜索换行符。在这种情况下,请注意在单元测试中覆盖访问文件的末尾。
- 使用
ByteBuffer.get
向前扫描,直到您找到 \n
。如果您有真正的 Unicode 文件,这可能不是一个选项,因为换行符 (0x0A) 的 Ascii 代码可以出现在其他地方,例如在字符代码为 0xAC0A 的 UTF-16 编码韩文音节中。
这将是第二种方法的 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")))
我正在使用 RandomAccessFile
从一个大文件中读取一些信息。
RandomAccessFile
有一个方法 seek
将光标指向我想要读取整行的文件的特定部分。要阅读这一行,我使用 readLine()
方法。
我之前阅读了整个文件,然后创建了一个索引,允许我使用 seek
方法访问任何行的开头。该索引工作正常。
我根据这个答案创建了这个索引:
因为我必须在这个文件中做很多访问,性能是一个需要注意的重要问题,然后我正在寻找其他选项来读取文件到特定行并获取整行。
我读到 FileChannel
和 MappedByteBuffer
是快速读取文件的好选择,但我没有看到任何解决方案满足我的要求。
P.S.: 线有不同的长度,我不知道这个长度。
有没有人有好的解决办法?
编辑:
我要读取的文件格式如下:key\t
value
索引是一个 hashmap,该文件的所有键都是键,值是字节位置 (Long
)。
假设我想转到带有键 "foo" 的行,那么我必须查找值位置,如下所示:
raf.seek(index.get("foo"))
如果我使用 raf.readLine()
,return 将是带有键 "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
即可找到您的密钥所在的 "page":position = keyIndex / pageSize * pageSize
,然后从该索引调用 buffer.position
:keyIndex - position
如果您真的没有办法将对一个 "page" 的访问分组在一起,那么您就不需要 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堆上创建的,实际上是OS级别的内存映射文件。 (从 Java 8 开始,您可以通过查看源代码并在实现中搜索 sun.nio.ch.DirectBuffer
来验证这一点)。
行大小: 获取行大小的最佳方法是在扫描文件时存储它,即使用 Map[String, (Long, Int)]
而不是您正在使用的内容现在 index
。如果这对您不起作用,您应该 运行 进行一些测试以找出更快的方法:
- 只需存储最大行大小,然后在这个最大长度的字符串中搜索换行符。在这种情况下,请注意在单元测试中覆盖访问文件的末尾。
- 使用
ByteBuffer.get
向前扫描,直到您找到\n
。如果您有真正的 Unicode 文件,这可能不是一个选项,因为换行符 (0x0A) 的 Ascii 代码可以出现在其他地方,例如在字符代码为 0xAC0A 的 UTF-16 编码韩文音节中。
这将是第二种方法的 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")))