我应该如何在scala中读取和编写大量文本文件,避免因巨大的内存需求而导致崩溃?
示例案例
输入文件有300万个字符串,以下代码显然崩溃
// The first line of the file contains input data type and total number of lines
val src = Source.fromFile("in.txt").getLines
val header = src.next.split(" ").toVector
val lines = (if (header(0) == "i") src.map(_.toInt) else src).toArray
process(lines) // no lines are removed during processing
val writer = new PrintWriter("out.txt", "UTF-8")
try writer.print(lines.mkString("\n"))
finally writer.close
我应该如何
答案 0 :(得分:1)
为此,您需要对数据进行流处理。这可以通过多种方式完成。使用专门处理流处理的库是非常的好方法。 scalaz-stream
就是这种图书馆的一个很好的例子。
他们的github页面提供了有关如何完全按照您的要求执行操作的示例代码。读入一个大的(可能是无限的)文件,对数据执行一些转换,所有转换都是持续的内存使用。
这是他们的README.md
示例的副本import scalaz.stream._
import scalaz.concurrent.Task
val converter: Task[Unit] =
io.linesR("testdata/fahrenheit.txt")
.filter(s => !s.trim.isEmpty && !s.startsWith("//"))
.map(line => fahrenheitToCelsius(line.toDouble).toString)
.intersperse("\n")
.pipe(text.utf8Encode)
.to(io.fileChunkW("testdata/celsius.txt"))
.run
// at the end of the universe...
val u: Unit = converter.run
您当然可以从标准库构建此类流处理。在这种情况下,你可能想要根据行或字节来处理它(前者更容易,后者更安全,因为不能保证即使在非常大的文件中也会有任何换行)。我个人强烈建议scalaz-stream
。
纯scala解决方案是使用scala.io.Source
之类的东西。例如,这将程序将计算角色的数量' a'发生在文件中,它将在常量内存中执行。
注意,您在示例中使用的是Source
,但是您正在调用将其转换为内存数据结构(特别是Array
不流式构造)。
import scala.io.Source
object Streaming extends App {
args.headOption.foreach{(file: String) =>
println(
s"Number of 'a' is: ${Source.fromFile(file).count(_ == 'a')}"
)
}
}
现在Source
非常容易使用,但它实际上只是为字符数据而设计的。如果您需要更强大的东西,也许处理任意二进制数据,那么您将需要使用Java标准库。
请注意,您可能会在标准库中使用其他原语来执行此操作,这些只是我选择的原语。他们使用我读过的java.nio包更加高效(我自己没有做任何基准测试)。
import java.nio.ByteBuffer
import java.nio.file.FileSystems
import java.nio.channels.ReadableByteChannel
import java.nio.file.Files
import java.nio.file.Path
object Streaming extends App {
@scala.annotation.tailrec
def countStuff(
buffer: ByteBuffer,
byteChannel: ReadableByteChannel,
count: BigInt
): BigInt = {
val newCount = byteChannel.read(buffer)
if (newCount == -1) {
println("Done reading")
count
} else {
println(s"Read ${newCount + count} bytes!")
buffer.clear()
countStuff(buffer, byteChannel, count + newCount)
}
}
args.headOption.foreach{(file: String) =>
val byteChannel =
Files.newByteChannel(FileSystems.getDefault().getPath(file))
countStuff(ByteBuffer.allocateDirect(1024), byteChannel, 0)
byteChannel.close()
}
}
计算它已读取的字节数,打印出当前计数,并在常量内存中执行此操作。显然这是一个非常无聊的用途,但你可以希望看到你如何根据自己的需要改变它。
你问为什么你不能在内存中对所有这些进行操作。您当然可以这样做,具体取决于您的可用内存(可通过JVM选项调整)和输入的大小。这里的答案假定您需要对任意大的数据进行操作。如果您不需要这样做,将整个语料库处理到内存中通常是更容易的解决方案。
同样,Scala / Java标准库示例仅在此处,因为您说不能使用类似scalaz-stream
的内容,这是我在这种情况下通常会执行的操作。