Scala:文件读取以构建外部合并排序

时间:2017-09-04 15:38:55

标签: scala functional-programming akka-stream fs2

我想在Scala中实现外部合并排序。它用于对大型文件进行排序,这些文件完全不适合主内存。

详情可在此处找到: - How external merge sort algorithm works?

现在,我需要读取文件的块,对其进行排序并将其写入磁盘等等

在部分中读取/写入大文件的最惯用/最实用的方法是什么?

  • 如果我使用'Source.fromFile(filename).getLines'方法,我知道我在文件上获得了一个迭代器,并且可以部分读取它。但是当我得到一个迭代器时,在主内存中读取了多少文件?是否可以从中读取固定数量的字节?
  • 有关如何实施此建议的任何其他建议?可能有一些指向fs2(scalaz-stream)/ Akka Stream / Monix实现的指针,我可以将文件视为Stream并以块读取?

1 个答案:

答案 0 :(得分:2)

分块排序/写作

假设您希望一次在内存中保留N个数字,并进一步假设您有一个函数将N个已排序的数字写入文件:

val N : Int = ???

val writeToFile : Seq[Int] => Unit = ???

如您的问题中所示,Iterator可以用于一次仅在RAM中保留N个数字以对它们进行排序并将它们写入中间文件:

val sourceFileName : String = ???

val sortAndWrite : Seq[Int] => Unit = 
  (_.sorted) andThen writeToFile

Source
  .fromFile(sourceFileName)
  .getLines
  .map(_.toInt)
  .grouped(N)
  .foreach(sortAndWrite)

现在,您将每组N个数字放在不同的文件中。剩下要做的就是将文件合并在一起。

<强>合并

给定一些读取函数,从每个子文件返回迭代器:

val subFiles : Iterable[Iterator[String]] = ???

我们可以编写一个函数来返回一个新的Iterator,它从每个文件中获取值并对它们进行排序:

val mergeSort : Iterable[Iterator[String]] => Iterator[Int] = 
  (fileIterators) => {

    val nonEmptyFiles = fileIterators filter (_.hasNext)

    nonEmptyFiles
      .map(_.next)
      .map(_.toInt)
      .sorted
      .toIterator ++ mergeSort(nonEmptyFiles)
  }

注意:上面的函数会在每个文件的内存中保留一个Integer,因此RAM的使用取决于writeToFile创建的不同文件的数量。

现在只需将值写入文件:

 val destinationFileName : String = ???

 val writer : Writer = new FileWriter(destinationFileName)

 mergeSort(subFiles) foreach (i => writer write i.toString)

不完整的排序

有一点需要注意:如果N很小并且源文件不够随机,那么解决方案将不会产生完美的排序。示例:假设N = 2且初始列表为[10,11,0,1],那么算法在一次通过后会生成[0,10,1,11]作为结果。