Scala:如何“预加载”懒惰迭代器的内容?

时间:2016-11-30 14:50:37

标签: scala collections iterator

假设我有一个懒惰的迭代器[Item]。只有当我们迭代迭代器时,才会懒惰地创建items对象。这些物品的制作成本很高。

我想将此迭代器序列化为JSON数组。它有效(使用Jackson scala模块),但在我看来效率不高。

据我了解,它目前的工作原理如下:

  • 计算下一个项目
  • 序列化项目
  • 计算下一个项目
  • 序列化项目
  • 计算下一个项目
  • 序列化项目

我希望项目的计算和项目的序列化并行发生。

我想要一个Iterator,它会在阅读下一个项目时开始计算一定数量的下一个项目。

例如我喜欢在执行iterator.next()时,在场景后面,接下来的50个项目得到计算,而不会阻塞迭代线程(它应该只等待下一个元素可用)。

我已经看过“BufferedIterator”,但它并不是我需要的,因为我并不想明确地查询“head”,而且我需要超过1个项目来预加载

有关如何实现这一目标的任何想法吗?

我也可以使用Stream替换Iterator的解决方案,但由于内存使用率较低而优先选择Iterator

1 个答案:

答案 0 :(得分:1)

如果我理解你的问题,那么这就是你可以做的一个例子。您可以将每个项目计算包装在Future中,这样您就可以在不阻塞的情况下迭代输入流,并在每个块准备好后对其进行处理/序列化。我将在REPL中执行此操作并在评估每件作品时进行打印,这样您就可以看到每件事情发生的时间:

@ import concurrent._, ExecutionContext.Implicits.global
import concurrent._, ExecutionContext.Implicits.global

@ def futureItem(i: Int): Future[Int] = Future { 
  Thread.sleep(1000)
  println(s"item: ${i}")
  i 
}
defined function futureItem

@ val inputIterator = (1 to 9).toIterator.map(futureItem)
inputIterator: Iterator[Future[Int]] = non-empty iterator

因此,计算每个项目至少需要1秒钟。现在我们想要以块的形式处理项目,这也需要一些时间:

@ def computeItemsChunk(items: Seq[Int]): Int = { 
  Thread.sleep(1000)
  val s = items.sum
  println(s"chunk ${items}: ${s}")
  s 
}
defined function computeItemsChunk

现在我们将输入流分组,应用Future.sequence并计算块:

@ case object foo {
  val chunksIterator = inputIterator.grouped(3).map { futureItems => 
    Future.sequence(futureItems).map(computeItemsChunk) 
  }
}
defined object foo

(我在一个对象中定义它,因为否则,分组(或其他)将强制评估第一个块)。现在让我们看看它是如何评估的:

@ Await.result(Future.sequence(foo.chunksIterator), Duration.Inf)
item: 2
item: 3
item: 4
item: 1
item: 7
item: 6
chunk List(1, 2, 3): 6
item: 5
item: 8
chunk List(4, 5, 6): 15
item: 9
chunk List(7, 8, 9): 24
res5: Iterator[Int] = non-empty iterator 

您可以看到,一旦项目可用就会计算块,并且迭代器会在不等待每个块评估的情况下前进。