public class WordCountReducer extends Reducer<Text, LongWritable, Text, LongWritable> {
@Override
protected void reduce(Text key, Iterable<LongWritable> 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<T> 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<String> iterator() {
class Iter implements Iterator<String> {
@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<String> iterator() {
class Iter implements Iterator<String> {
@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 循环,都不会进入循环体中。