我收到一个迭代器作为参数,我想迭代两次值。
public void reduce(Pair<String,String> key, Iterator<IntWritable> values,
Context context)
有可能吗?怎么样 ? 签名是由我正在使用的框架(即Hadoop)强加的。
- 编辑 -
最后,reduce
方法的真实签名是iterable
。我被这个wiki page误导了(这实际上是我发现的唯一一个非弃用的(但错误的)wordcount示例。)
答案 0 :(得分:13)
不幸的是,如果没有像Andreas_D的回答那样缓存值,这是不可能的。
即使使用新的API,Reducer
收到Iterable
而不是Iterator
,您也无法迭代两次。尝试类似的事情非常诱人:
for (IntWritable value : values) {
// first loop
}
for (IntWritable value : values) {
// second loop
}
但这实际上不起作用。您从Iterator
的{{1}}方法获得的Iterable
是特殊的。这些值可能并非都在内存中; Hadoop可能正在从磁盘流式传输它们。它们并非真正由iterator()
支持,因此允许多次迭代非常重要。
您可以在Collection
和Reducer
代码中自行查看。
在某种ReduceContext
中缓存值可能是最简单的答案,但如果您在大型数据集上操作,则可以轻松地将堆烧掉。如果您能就我们的问题提供更多具体信息,我们可以帮助您找到一个不涉及多次迭代的解决方案。
答案 1 :(得分:10)
重用给定的迭代器,没有。
但是当你在第一个位置迭代它们然后迭代构造的ArrayList时,你可以将值保存在ArrayList中(或者你可以通过使用一些花哨的Collection方法直接构建它,然后迭代直接在ArrayList上两次。这是一个品味问题。)
无论如何,你确定通过Iterator是一件好事吗? 迭代器习惯于对集合进行线性扫描,这就是为什么它们不暴露“倒带”方法。
您应该传递不同的内容,例如Collection<T>
或Iterable<T>
,这已在其他答案中提出。
答案 2 :(得分:10)
如果要再次迭代,我们必须缓存迭代器中的值。至少我们可以将第一次迭代和缓存结合起来:
Iterator<IntWritable> it = getIterator();
List<IntWritable> cache = new ArrayList<IntWritable>();
// first loop and caching
while (it.hasNext()) {
IntWritable value = it.next();
doSomethingWithValue();
cache.add(value);
}
// second loop
for(IntWritable value:cache) {
doSomethingElseThatCantBeDoneInFirstLoop(value);
}
(只是为了添加代码答案,知道您在自己的评论中提到了这个解决方案;)
为什么没有缓存是不可能的:Iterator
是实现接口的东西,没有一个要求,Iterator
对象实际存储了值。迭代两次你必须重置迭代器(不可能)或克隆它(再次:不可能)。
举一个迭代器的例子,其中克隆/重置没有任何意义:
public class Randoms implements Iterator<Double> {
private int counter = 10;
@Override
public boolean hasNext() {
return counter > 0;
}
@Override
public boolean next() {
count--;
return Math.random();
}
@Override
public boolean remove() {
throw new UnsupportedOperationException("delete not supported");
}
}
答案 3 :(得分:6)
迭代器只是一次遍历。 某些迭代器类型是可克隆的,您可以在遍历之前克隆它,但这不是一般情况。
你应该让你的函数取代Iterable
,如果你能实现的话。
答案 4 :(得分:2)
如果方法签名无法更改,那么我建议使用Apache Commons IteratorUtils将Iterator转换为ListIterator。考虑这个示例方法,对值进行两次迭代:
void iterateTwice(Iterator<String> it) {
ListIterator<?> lit = IteratorUtils.toListIterator(it);
System.out.println("Using ListIterator 1st pass");
while(lit.hasNext())
System.out.println(lit.next());
// move the list iterator back to start
while(lit.hasPrevious())
lit.previous();
System.out.println("Using ListIterator 2nd pass");
while(lit.hasNext())
System.out.println(lit.next());
}
使用上面的代码,我能够遍历值列表,而不用在我的代码中保存List元素的副本。
答案 5 :(得分:1)
如果我们尝试在Reducer中迭代两次,如下所示
ListIterator<DoubleWritable> lit = IteratorUtils.toListIterator(it);
System.out.println("Using ListIterator 1st pass");
while(lit.hasNext())
System.out.println(lit.next());
// move the list iterator back to start
while(lit.hasPrevious())
lit.previous();
System.out.println("Using ListIterator 2nd pass");
while(lit.hasNext())
System.out.println(lit.next());
我们只会输出
Using ListIterator 1st pass
5.3
4.9
5.3
4.6
4.6
Using ListIterator 2nd pass
5.3
5.3
5.3
5.3
5.3
为了以正确的方式得到它,我们应该像这样循环:
ArrayList<DoubleWritable> cache = new ArrayList<DoubleWritable>();
for (DoubleWritable aNum : values) {
System.out.println("first iteration: " + aNum);
DoubleWritable writable = new DoubleWritable();
writable.set(aNum.get());
cache.add(writable);
}
int size = cache.size();
for (int i = 0; i < size; ++i) {
System.out.println("second iteration: " + cache.get(i));
}
输出
first iteration: 5.3
first iteration: 4.9
first iteration: 5.3
first iteration: 4.6
first iteration: 4.6
second iteration: 5.3
second iteration: 4.9
second iteration: 5.3
second iteration: 4.6
second iteration: 4.6
答案 6 :(得分:1)
你可以那样做
MarkableIterator<Text> mitr = new MarkableIterator<Text>(values.iterator());
mitr.mark();
while (mitr.hasNext())
{
//do your work
}
mitr.reset();
while(mitr.hasNext())
{
//again do your work
}
答案 7 :(得分:1)
注意::如果您使用缓存列表来缓存项目,则应首先克隆该项目,然后将其添加到缓存中。否则,您将在缓存中找到所有相同的项目。
这种情况是由MapReduce的内存优化引起的,在reduce方法中,Iterable重用item实例,有关更多详细信息,请参见here
答案 8 :(得分:0)
试试这个:
ListIterator it = list.listIterator();
while(it.hasNext()){
while(it.hasNext()){
System.out.println("back " + it.next() +" ");
}
while(it.hasPrevious()){
it.previous();
}
}
答案 9 :(得分:0)
如果您想要随时更改值,我认为使用listIterator然后使用其set()方法会更好。
ListIterator lit = list.listIterator();
while(lit.hasNext()){
String elem = (String) lit.next();
System.out.println(elem);
lit.set(elem+" modified");
}
lit = null;
lit = list.listIterator();
while(lit.hasNext()){
System.out.println(lit.next());
}
不是调用.previous(),而是在同一个列表迭代器对象上获取.listIterator()的另一个实例。
答案 10 :(得分:0)
在搜索并做了很多尝试和错误后,我找到了解决方案。
宣布一个新的集合(比如cache
)(链表或Arraylist或其他任何内容)
在第一次迭代中,分配当前的迭代器,如下例所示:
cache.add(new Text(current.get()))
迭代缓存:
for (Text count : counts) {
//counts is iterable object of Type Text
cache.add(new Text(count.getBytes()));
}
for(Text value:cache) {
// your logic..
}