请不要盲目地关闭这个问题,在所谓的重复不要工作中给出的所有答案。 OP负责另一个问题,显然,答案符合HIS问题,但不符合我的问题。
并非每个类似的问题都是重复的,SE上有“扩展问题”这样的功能,唯一的方法是再次询问同一主题,以获得不同的,有效的答案。
我有迭代器。我想得到它的复制(复制),然后我可以完全独立地继续原始和复制。
重要
通过反射或序列化进行复制是不行的(性能损失)。
示例
var list = List(1,2,3,4,5)
var it1 = list.iterator
it1.next()
var it2 = it1 // (*)
it2.next()
println(it1.next())
这只会引用 it1 ,因此在更改 it1 时, it2 也会发生变化,反之亦然。
上面的示例使用列表,我目前正在努力解决 HashMap 问题,但问题是一般问题 - 只是迭代器。
如果你编辑行(*)并写:
var it2 = it1.toList.iterator
(这被建议作为链接问题中的解决方案)执行程序时抛出异常。
“你拿着名单......”。不,我没有。我没有列表,我有迭代器。一般来说,我不知道任何关于迭代器的基础的集合,我唯一拥有的是迭代器。我必须“分叉”它。
答案 0 :(得分:14)
您不能在不破坏它的情况下复制迭代器。 iterator
的合同是它只能遍历一次。
您链接的问题显示了如何获取两个副本以换取您销毁的副本。您无法继续使用原件,但现在可以独立向前运行两个新副本。
答案 1 :(得分:9)
创建一个可以复制而不会破坏它的List
迭代器非常容易:这基本上是从iterator
源复制的List
方法的定义{{1添加了方法:
fork
使用:
class ForkableIterator[A] (list: List[A]) extends Iterator[A] {
var these = list
def hasNext: Boolean = !these.isEmpty
def next: A =
if (hasNext) {
val result = these.head; these = these.tail; result
} else Iterator.empty.next
def fork = new ForkableIterator(these)
}
我看过为scala> val it = new ForkableIterator(List(1,2,3,4,5,6))
it: ForkableIterator[Int] = non-empty iterator
scala> it.next
res72: Int = 1
scala> val it2 = it.fork
it2: ForkableIterator[Int] = non-empty iterator
scala> it2.next
res73: Int = 2
scala> it2.next
res74: Int = 3
scala> it.next
res75: Int = 2
执行此操作但看起来更复杂(部分原因是根据集合大小有不同的地图实现)。因此,最好在HashMap
上使用上述实现。
答案 2 :(得分:6)
正如雷克斯所说,在不破坏它的情况下制作迭代器的副本是不可能的。也就是说,duplicate
会出现什么问题?
var list = List(1,2,3,4,5)
var it1 = list.iterator
it1.next()
val (it1a, it1b) = it1.duplicate
it1 = it1a
var it2 = it1b
it2.next()
println(it1.next())
答案 3 :(得分:0)
我认为这是一个非常好的问题,很遗憾,许多人不了解问题的价值。在大数据时代,很多情况下我们都有一个流,而不是无法收集或无法放入内存的已分配数据列表。而且从一开始就重复进行它也是昂贵的。如果我们需要对数据进行两次(或更多次)单独的计算,该怎么办?例如,我们可能需要使用已经编写的函数来计算最小值,最大值,总和,md5等,而在不同线程中只有一次通过。
一般的解决方案是使用Akka-Stream。这样就可以了。 但是,使用Iterator可能吗?这是Java / Scala中表示此类流数据源的最简单方法吗? 答案是肯定的,尽管我们“不能继续进行原著并完全独立地进行复制”,这意味着我们必须同步每个使用者线程的消耗速度。 (Akka-Stream通过使用背压和一些中间缓冲区来实现此目的。)
所以这是我的简单解决方案:使用Phaser。有了它,我们可以使Iterator包装器遍历一遍源。该对象将在每个使用者线程中用作简单的Iterator。使用它,您将提前知道消耗线程的数量。同样,每个使用者线程都必须耗尽源,直到结束为止,以免造成所有问题(例如使用flush()方法)。
import java.util.concurrent.Phaser
import java.util.concurrent.atomic.AtomicBoolean
// it0 - input source iterator
// num - exact number of consuming threads. We have to know it in advance.
case class ForkableIterator[+A]( it0: Iterator[A], num: Int ) extends Phaser(num) with Iterator[A] {
val it = it0.flatMap( Stream.fill(num)(_) ) // serial replicator
private var hasNext0 = new AtomicBoolean( it0.hasNext )
override def hasNext: Boolean = hasNext0.get()
override def next(): A = {
arriveAndAwaitAdvance()
val next = it.synchronized {
val next = it.next()
if (hasNext0.get) hasNext0.set(it.hasNext)
next
}
arriveAndAwaitAdvance() // otherwise the tasks locks at the end the last data element
next
}
// In case that a consumer gives up to read before the end of its source data stream occurs
// it HAVE to drain the last to avoid block others. (Note: Phaser has no "unregister" method?).
// Calling it may be avoided if all consumers read exactly the same amount of data,
// e.g. until the very end of it.
def flush(): Unit = while (hasNext) next()
}
PS我成功地将这个“ ForkableIterator”与Spark配合使用,以对较长的源数据流执行几个独立的聚合。在这种情况下,我不必手动创建线程。您也可以使用Scala Futures / Monix Tasks等。
PSPS现在,我重新检查了JDK Phaser规范,发现它实际上具有名为“ unknownAndDeregister()”的“取消注册”方法。因此,如果使用者完成,请使用它代替flush()。