记一次在 Reduce 中 多次对 values 遍历产生的问题

public class WordCountReducer extends Reducer {

    @Override
    protected void reduce(Text key, Iterable values, Context context)
            throws IOException, InterruptedException {

        long sum = 0;

        for (LongWritable value : values) {
            sum += value.get();
        }

        context.write(key, new LongWritable(sum));
    }
}

上面代码中,不能对 values 多次进行 foreach 遍历,第一次遍历之后,再遍历的话,会因为 iterator 中记录当前访问位置的变量,已经到达末尾,而不进入循环体,

values是 Iterable,Iterable中有一个方法:
Iterator iterator();
可以获取 iterator,foreach是jdk5出的新特性,foreach 最终会翻译成 下面代码来实现:

while (iterator.hasNext) {
    iterator.next();
    //...
}

我们再来回到文章题目,为什么 不能多次 对 values 进行遍历呢?
我们通过一个例子来模拟 mapreduce中的 values 是怎么定义的:
首先自定义一个类,实现了 Iterable 接口:

public class IterableTest implements Iterable {

    private Object[] obj = new Object[1];
    //记录添加元素的个数
    private int size;//记录当前元素的下标
    private int current = 0;

    //添加元素
    public void add(String str) {//判断数组是否已经满了如果满了扩张数组
        if (size == obj.length) {
            //扩张数组到一个新长度
            obj = Arrays.copyOf(obj, obj.length + obj.length << 1);
        }
        obj[size++] = str;
    }  //重写iterator方法

    public Iterator iterator() {

        class Iter implements Iterator {

            @Override
            public boolean hasNext() {
                // 判断当前指针是否小于实际大小
                if (current < size) {
                    return true;
                }
                return false;
            }

            @Override
            public String next() {
                // 返回当前元素,并把当前下标前移
                return obj[current++].toString();
            }

            @Override
            public void remove() {
                // TODO Auto-generated method stub
            }
        }

        //如果不指定 current = 0,那下次再获取 iterator时,因为 current
        // 指向最后一个,所以调用 hasNext会直接退出。
        //current = 0;
        return new Iter();
    }
}

然后在main中测试一下:

public static void main(String[] args) {
        IterableTest iterable = new IterableTest();
        iterable.add("aaa");
        iterable.add("ccc");
        iterable.add("ddd");
        iterable.add("eee");

        for (Object object : iterable) {
            System.out.println(object);
        }

        System.out.println("1111111111111");
        for (Object object : iterable) {
            System.out.println(object);
        }

        System.out.println("2222222222");

        Iterator iterator = iterable.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        System.out.println("33333333333333");

        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }

结果为:

aaa
ccc
ddd
eee
1111111111111
2222222222
33333333333333

我们看到,111111 后面的都不进行遍历了,为什么呢?

因为每次for循环,其实还是调用的 IterableTest 的 iterator(),因为 current 已经移到了末尾,便不会满足 IterableTest 的 iterator() 方法中,定义的 Iter 类里,hasNext 条件,返回 false,退出 while 循环,也就是 foreach 的本质:

while (iterator.hasNext) {
    iterator.next();
    //...
}

我们再来看一下 IterableTest 的 iterator() 方法:

public Iterator iterator() {

    class Iter implements Iterator {
        @Override
        public boolean hasNext() {
            // 判断当前指针是否小于实际大小
            if (current < size) {
                return true;
            }
            return false;
        }

        @Override
        public String next() {
            // 返回当前元素,并把当前下标前移
            return obj[current++].toString();
        }

        @Override
        public void remove() {
        }
    }

    //如果不指定 current = 0,那下次再获取 iterator时,因为 current
    // 指向最后一个,所以调用 hasNext会直接退出。
    //current = 0;
    return new Iter();
}

我们看上面代码最后的注释,因为第一次 for 遍历后,current 记录的值 等于 size,所以当再次 for循环,创建一个 Iter对象,当执行 hasNext时,不满足:

public boolean hasNext() {
    // current 已经为 size,不满足条件,返回 false
    if (current < size) {
        return true;
    }
    return false;
}

所以后面的 for 循环,都不会进入循环体中。

你可能感兴趣的:(记一次在 Reduce 中 多次对 values 遍历产生的问题)