在值上迭代两次(MapReduce)

时间:2011-05-24 13:35:18

标签: java iterator hadoop

我收到一个迭代器作为参数,我想迭代两次值。

public void reduce(Pair<String,String> key, Iterator<IntWritable> values,
                   Context context)

有可能吗?怎么样 ? 签名是由我正在使用的框架(即Hadoop)强加的。

- 编辑 -
最后,reduce方法的真实签名是iterable。我被这个wiki page误导了(这实际上是我发现的唯一一个非弃用的(但错误的)wordcount示例。)

11 个答案:

答案 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()支持,因此允许多次迭代非常重要。

您可以在CollectionReducer代码中自行查看。

在某种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
}
  1. Reference Link 2

  2. Reference Link 2

答案 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)

在搜索并做了很多尝试和错误后,我找到了解决方案。

  1. 宣布一个新的集合(比如cache)(链表或Arraylist或其他任何内容)

  2. 在第一次迭代中,分配当前的迭代器,如下例所示:

    cache.add(new Text(current.get()))  
    
  3. 迭代缓存:

    for (Text count : counts) {
        //counts is iterable object of Type Text
        cache.add(new Text(count.getBytes()));
    }
    for(Text value:cache) {
        // your logic..
    }