Hadoop提供的reduce函數中Iterable 接口只能遍歷一次的問題

之前有童鞋問到了這樣一個問題:爲什麼我在 reduce 階段遍歷了一次 Iterable 之後,再次遍歷的時候,數據都沒了呢?可能有童鞋想當然的回答:Iterable 只能單向遍歷一次,就這樣簡單的原因。。。事實果真如此嗎?

還是用代碼說話:

[java] view plain copy
  1. package com.test;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Iterator;  
  5. import java.util.List;  
  6.   
  7. public class T {  
  8.   
  9.     public static void main(String[] args) {  
  10.   
  11.         // 只要實現了Iterable接口的對象都可以使用for-each循環。  
  12.         // Iterable接口只由iterator方法構成,  
  13.         // iterator()方法是java.lang.Iterable接口,被Collection繼承。  
  14.         /*public interface Iterable<T> { 
  15.             Iterator<T> iterator(); 
  16.         }*/  
  17.         Iterable<String> iter = new Iterable<String>() {  
  18.             public Iterator<String> iterator() {  
  19.                 List<String> l = new ArrayList<String>();  
  20.                 l.add("aa");  
  21.                 l.add("bb");  
  22.                 l.add("cc");  
  23.                 return l.iterator();  
  24.             }  
  25.         };  
  26.         for(int count : new int[] {12}){  
  27.             for (String item : iter) {  
  28.                 System.out.println(item);  
  29.             }  
  30.             System.out.println("---------->> " + count + " END.");  
  31.         }  
  32.     }  
  33. }  

結果當然是很正常的完整無誤的打印了兩遍  Iterable 的值。那究竟是什麼原因導致了 reduce 階段的  Iterable 只能被遍歷一次呢?

我們先看一段測試代碼:

測試數據:

[java] view plain copy
  1. 3  
  2. 4  
  3. 50  
  4. 60  
  5. 70  
  6. 8  
  7. 9  
[java] view plain copy
  1. <pre name="code" class="java">import java.io.IOException;  
  2. import java.util.ArrayList;  
  3. import java.util.List;  
  4.   
  5. import org.apache.hadoop.conf.Configuration;  
  6. import org.apache.hadoop.fs.FileSystem;  
  7. import org.apache.hadoop.fs.Path;  
  8. import org.apache.hadoop.io.Text;  
  9. import org.apache.hadoop.mapreduce.Job;  
  10. import org.apache.hadoop.mapreduce.Mapper;  
  11. import org.apache.hadoop.mapreduce.Reducer;  
  12. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
  13. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
  14. import org.apache.hadoop.util.GenericOptionsParser;  
  15.   
  16. public class TestIterable {  
  17.   
  18.     public static class M1 extends Mapper<Object, Text, Text, Text> {  
  19.         private Text oKey = new Text();  
  20.         private Text oVal = new Text();  
  21.         String[] lineArr;  
  22.   
  23.         public void map(Object key, Text value, Context context) throws IOException, InterruptedException {  
  24.             lineArr = value.toString().split(" ");  
  25.             oKey.set(lineArr[0]);  
  26.             oVal.set(lineArr[1]);  
  27.             context.write(oKey, oVal);  
  28.         }  
  29.     }  
  30.   
  31.     public static class R1 extends Reducer<Text, Text, Text, Text> {  
  32.         List<String> valList = new ArrayList<String>();  
  33.         List<Text> textList = new ArrayList<Text>();  
  34.         String strAdd;  
  35.         public void reduce(Text key, Iterable<Text> values, Context context) throws IOException,  
  36.                 InterruptedException {  
  37.             valList.clear();  
  38.             textList.clear();  
  39.             strAdd = "";  
  40.             for (Text val : values) {  
  41.                 valList.add(val.toString());  
  42.                 textList.add(val);  
  43.             }  
  44.               
  45.             // 坑之 1 :爲神馬輸出的全是最後一個值?why?  
  46.             for(Text text : textList){  
  47.                 strAdd += text.toString() + ", ";  
  48.             }  
  49.             System.out.println(key.toString() + "\t" + strAdd);  
  50.             System.out.println(".......................");  
  51.               
  52.             // 我這樣幹呢?對了嗎?  
  53.             strAdd = "";  
  54.             for(String val : valList){  
  55.                 strAdd += val + ", ";  
  56.             }  
  57.             System.out.println(key.toString() + "\t" + strAdd);  
  58.             System.out.println("----------------------");  
  59.               
  60.             // 坑之 2 :第二次遍歷的時候爲什麼得到的都是空?why?  
  61.             valList.clear();  
  62.             strAdd = "";  
  63.             for (Text val : values) {  
  64.                 valList.add(val.toString());  
  65.             }  
  66.             for(String val : valList){  
  67.                 strAdd += val + ", ";  
  68.             }  
  69.             System.out.println(key.toString() + "\t" + strAdd);  
  70.             System.out.println(">>>>>>>>>>>>>>>>>>>>>>");  
  71.         }  
  72.     }  
  73.   
  74.     public static void main(String[] args) throws Exception {  
  75.         Configuration conf = new Configuration();  
  76.         conf.set("mapred.job.queue.name""regular");  
  77.         String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();  
  78.         if (otherArgs.length != 2) {  
  79.             System.err.println("Usage: wordcount <in> <out>");  
  80.             System.exit(2);  
  81.         }  
  82.         System.out.println("------------------------");  
  83.         Job job = new Job(conf, "TestIterable");  
  84.         job.setJarByClass(TestIterable.class);  
  85.         job.setMapperClass(M1.class);  
  86.         job.setReducerClass(R1.class);  
  87.         job.setOutputKeyClass(Text.class);  
  88.         job.setOutputValueClass(Text.class);  
  89.         // 輸入輸出路徑  
  90.         FileInputFormat.addInputPath(job, new Path(otherArgs[0]));  
  91.         FileSystem.get(conf).delete(new Path(otherArgs[1]), true);  
  92.         FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));  
  93.         System.exit(job.waitForCompletion(true) ? 0 : 1);  
  94.     }  
  95. }  



在 Eclipse 控制檯中的結果如下:

[java] view plain copy
  1. a   9999,   
  2. .......................  
  3. a   34709,   
  4. ----------------------  
  5. a     
  6. >>>>>>>>>>>>>>>>>>>>>>  
  7. b   888,   
  8. .......................  
  9. b   50608,   
  10. ----------------------  
  11. b     
  12. >>>>>>>>>>>>>>>>>>>>>>  


關於第 1 個坑:對象重用( objects reuse 

reduce方法的javadoc中已經說明了會出現的問題: 

The framework calls this method for each <key, (list of values)> pair in the grouped inputs. Output values must be of the same type as input values. Input keys must not be altered. The framework will reuse the key and value objects that are passed into the reduce, therefore the application should clone the objects they want to keep a copy of.

      也就是說雖然reduce方法會反覆執行多次,但key和value相關的對象只有兩個,reduce會反覆重用這兩個對象。所以如果要保存key或者value的結果,只能將其中的值取出另存或者重新clone一個對象(例如Text store = new Text(value) 或者 String a = value.toString()),而不能直接賦引用。因爲引用從始至終都是指向同一個對象,你如果直接保存它們,那最後它們都指向最後一個輸入記錄。會影響最終計算結果而出錯。 

看到這裏,我想你會恍然大悟:這不是剛畢業找工作,面試官常問的問題:String 是不可變對象但爲什麼能相加呢?爲什麼字符串相加不提倡用 String,而用 StringBuilder ?如果你還不清楚這個問題怎麼回答,建議你看看這篇深入理解 String, StringBuffer 與 StringBuilder 的區別http://my.oschina.net/leejun2005/blog/102377

關於第 2 個坑:http://stackoverflow.com/questions/6111248/iterate-twice-on-values

The Iterator you receive from that Iterable's iterator() method is special. The values may not all be in memory; Hadoop may be streaming them from disk. They aren't really backed by a Collection, so it's nontrivial to allow multiple iterations.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章