作为一名C#程序员,我对Java / Scala迭代器设计有一个粗略的理解。
我试图(懒惰 - 因为源可能很大)从RecordReader
(在某些第三方库中)读取记录。我需要每100条记录做一些额外的工作。
for (group <- reader.iterator.zipWithIndex.grouped(100)) {
for ((record, i) <- group) {
println(i + "|" + record.key)
}
// ...
}
每次都会反复给我最后一条记录。
如果我不使用grouped
,它可以正常工作,我会得到每条记录。我错过了关于延迟流式传输或Java迭代器的事情吗?
答案 0 :(得分:3)
我认为问题可能是Record.key
只返回某个变量的当前值,该变量在迭代器被消耗时发生变异
(而不是让记录在施工时实际捕获关键值)。
一个例子可能会让它更清晰。
首先,让我们使用scala REPL来编写一些不显示问题的测试代码:
case class Record( key: Int )
def getRecordIterator: Iterator[Record] = {
var currentKey: Int = 0
(1 to 10).iterator.map{ i =>
currentKey += 1
new Record( currentKey )
}
}
然后我们可以尝试迭代而不使用grouped
:
for ((record, i) <- getRecordIterator.zipWithIndex) {
println(i + "|" + record)
}
这给了我们(正如预期的那样)
0|Record(1)
1|Record(2)
2|Record(3)
3|Record(4)
4|Record(5)
5|Record(6)
6|Record(7)
7|Record(8)
8|Record(9)
9|Record(10)
然后使用grouped
:
for (group <- getRecordIterator.zipWithIndex.grouped(3)) {
for ((record, i) <- group) {
println(i + "|" + record)
}
println("---")
}
给出了:
0|Record(1)
1|Record(2)
2|Record(3)
---
3|Record(4)
4|Record(5)
5|Record(6)
---
6|Record(7)
7|Record(8)
8|Record(9)
---
9|Record(10)
---
到现在为止,一切都很好。
现在让我们稍微改变Record
的定义:
trait Record {
def key: Int
override def toString = "Record(" + key + ")"
}
def getRecordIterator: Iterator[Record] = {
var currentKey: Int = 0
(1 to 10).iterator.map{ i =>
currentKey += 1
new Record{ def key = currentKey }
}
}
通过此更改,我们在不使用grouped
时仍然会得到相同的结果,但这是我们在使用group
时获得的结果:
0|Record(3)
1|Record(3)
2|Record(3)
---
3|Record(6)
4|Record(6)
5|Record(6)
---
6|Record(9)
7|Record(9)
8|Record(9)
---
9|Record(10)
---
问题的根源在于,在迭代器上调用next
这一事实会改变Record.get
返回的值。
这个问题可以更加轻松地说明:
val it = getRecordIterator
val r1 = it.next
println(r1) // prints "Record(1)" as expected
val r2 = it.next
println(r2) // prints "Record(2)" as expected
println(r1) // this now prints "Record(2)", not "Record(1)" anymore!
答案 1 :(得分:2)
要进行故障排除,请尝试在另一个打印正在进行的内容的迭代器中修饰迭代器:
def wrap[T](i: Iterator[T]) = new Iterator[T] {
def hasNext = { val b = i.hasNext; println("hasNext => " + b); b }
def next() = { val n = i.next(); println("next() => " + n); n }
}
val reader = Iterator.from(20).take(10).toList
for (group <- wrap(reader.iterator).zipWithIndex.grouped(5)) {
for ((v, i) <- group) println("[" + i + "] = " + v)
}
首次实例化迭代器时,在迭代器上调用wrap。这将打印如下:
hasNext => true
hasNext => true
next() => 20
hasNext => true
next() => 21
hasNext => true
这可以帮助您确定迭代器是否表现不佳......例如,如果没有调用hasNext
,库可能无法正确处理多次调用next
。在这种情况下,您可以修改wrap
,以便使迭代器正常运行。还有一件事,从症状来看,感觉你已经在调用分组之前使用了迭代器。所以要格外小心并检查之前是否使用过相同的迭代器引用。