分析Hadoop自带WordCount例子的执行过程

在Hadoop的发行包中也附带了例子的源代码,WordCount.java类的主函数实现如下所示:

Java代码   收藏代码
  1. public   static   void  main(String[] args)  throws  Exception {  
  2.     int  res = ToolRunner.run( new  Configuration(),  new  WordCount(), args);  
  3.     System.exit(res);  
  4. }  
  5.   
  6. }  
public static void main(String[] args) throws Exception {
    int res = ToolRunner.run(new Configuration(), new WordCount(), args);
    System.exit(res);
}

}

 

我们先从主函数入手吧,一点点地按照“深度遍历”的思想,分解掉这个WordCount字频统计工具,让我们更清晰地看到到底在Hadoop中是如何进行工作的。

首先从ToolRunner的run方法开始,run方法需要三个参数,第一个是一个Configuration类的实例。第二个是 WorCount类的实例,args就是从控制台接收到的命令行数组。可见,估计分析到我们的WordCount还非常非常的远,因为 Configuration类与args数组就够追踪一会了。

下面是ToolRunner的run方法的实现:

Java代码   收藏代码
  1. public   static   int  run(Configuration conf, Tool tool, String[] args)   
  2.     throws  Exception{  
  3.     if (conf ==  null ) {  // 即使传入的conf为null,仍然会在这里实例化一个配置类Configuration的对象   
  4.       conf = new  Configuration();  
  5.     }  
  6.     GenericOptionsParser parser = new  GenericOptionsParser(conf, args);  // 根据指定的conf和args数组实例化一个GenericOptionsParser类的对象,构造GenericOptionsParser类对象能实现对Hadoop通用的配置信息进行解析   
  7.     // Tool类是一个接口,WordCount工具就是实现了Tool接口,Tool接口中只是定义了一个run方法,即实现一个Tool必须要知道这个Tool的实现类的对象怎样run。   
  8.   
  9.     // 因为Tool接口实现了Configurable接口,在Configurable接口中可以为一个Tool设置初始化配置,即使用setConf()方法   
  10.     tool.setConf(conf);  
  11.       
  12.     //get the args w/o generic hadoop args   
  13.     String[] toolArgs = parser.getRemainingArgs(); // 返回从控制台输入的命令行参数的数组   
  14.     return  tool.run(toolArgs);  // 根据toolArgs数组指定的命令启动WordCount实例运行,返回实现Tool接口的实现类的对象的执行状态码   
  15. }  
public static int run(Configuration conf, Tool tool, String[] args) 
    throws Exception{
    if(conf == null) { // 即使传入的conf为null,仍然会在这里实例化一个配置类Configuration的对象
      conf = new Configuration();
    }
    GenericOptionsParser parser = new GenericOptionsParser(conf, args); // 根据指定的conf和args数组实例化一个GenericOptionsParser类的对象,构造GenericOptionsParser类对象能实现对Hadoop通用的配置信息进行解析
    // Tool类是一个接口,WordCount工具就是实现了Tool接口,Tool接口中只是定义了一个run方法,即实现一个Tool必须要知道这个Tool的实现类的对象怎样run。

    // 因为Tool接口实现了Configurable接口,在Configurable接口中可以为一个Tool设置初始化配置,即使用setConf()方法
    tool.setConf(conf);
    
    //get the args w/o generic hadoop args
    String[] toolArgs = parser.getRemainingArgs(); // 返回从控制台输入的命令行参数的数组
    return tool.run(toolArgs); // 根据toolArgs数组指定的命令启动WordCount实例运行,返回实现Tool接口的实现类的对象的执行状态码
}

 

上面的run方法应该是执行WordCount例子的最高层的方法,最抽象了。

程序一开始,首先要解析Hadoop配置文件,对应于Hadoop根目录下的conf目录下。其中的配置类为Configuration,构造一个Configuration对象,使用如下所示构造方法:

Java代码   收藏代码
  1.    public  Configuration() {  
  2.     if  (LOG.isDebugEnabled()) {  
  3.       LOG.debug(StringUtils.stringifyException(new  IOException( "config()" )));  
  4.     }  
  5.     resources.add("hadoop-default.xml" );  
  6.     resources.add("hadoop-site.xml" );  
  7. }  
  public Configuration() {
    if (LOG.isDebugEnabled()) {
      LOG.debug(StringUtils.stringifyException(new IOException("config()")));
    }
    resources.add("hadoop-default.xml");
    resources.add("hadoop-site.xml");
}

 

实例化一个Configuration对象,就是将conf目录中的hadoop-default.xml和hadoop-site.xml配置文件加入到private ArrayList<Object> resources中,以便再进一步解析。

真正解析Hadoop的配置文件的是一个GenericOptionsParser通用选项解析器类,需要提供一个Configuration对象的,同时指定一个命令行参数数组。

如下是GenericOptionsParser类的构造方法:

Java代码   收藏代码
  1.   public  GenericOptionsParser(Configuration conf, String[] args) {  
  2.     this (conf,  new  Options(), args);  // 这里额外又多增加了一个Options对象作为参数   
  3. }  
 public GenericOptionsParser(Configuration conf, String[] args) {
    this(conf, new Options(), args); // 这里额外又多增加了一个Options对象作为参数
}

 

Options类是一个选项对象的集合,用于描述在应用中可能使用到的命令行参数。可以通过查看Options类的构造方法:

Java代码   收藏代码
  1. public  Options()  
  2.   {  
  3.       // nothing to do   
  4.   }  
  public Options()
    {
        // nothing to do
    }

 

其实,什么也没有做。然而,可以动态为一个Options对象添加指定的选项的。

又调用了GenericOptionsParser类的另一个构造方法,如下所示:

Java代码   收藏代码
  1.    public  GenericOptionsParser(Configuration conf, Options options, String[] args) {  
  2.     parseGeneralOptions(options, conf, args);  
  3. }  
  public GenericOptionsParser(Configuration conf, Options options, String[] args) {
    parseGeneralOptions(options, conf, args);
}

 

继续调用GenericOptionsParser类的成员方法parseGeneralOptions()来进一步解析配置选项:

Java代码   收藏代码
  1. /**  
  2.   * Parse the user-specified options, get the generic options, and modify  
  3.   * configuration accordingly  
  4.   * @param conf Configuration to be modified  
  5.   * @param args User-specified arguments  
  6.   * @return Command-specific arguments  
  7.   */   
  8. rivate String[] parseGeneralOptions(Options opts, Configuration conf,   
  9.      String[] args) {  
  10.    opts = buildGeneralOptions(opts);  
  11.    CommandLineParser parser = new  GnuParser();  
  12.    try  {  
  13.      commandLine = parser.parse(opts, args, true );  
  14.      processGeneralOptions(conf, commandLine);  
  15.      return  commandLine.getArgs();  
  16.    } catch (ParseException e) {  
  17.      LOG.warn("options parsing failed: " +e.getMessage());  
  18.   
  19.      HelpFormatter formatter = new  HelpFormatter();  
  20.      formatter.printHelp("general options are: " , opts);  
  21.    }  
  22.    return  args;  
 /**
   * Parse the user-specified options, get the generic options, and modify
   * configuration accordingly
   * @param conf Configuration to be modified
   * @param args User-specified arguments
   * @return Command-specific arguments
   */
private String[] parseGeneralOptions(Options opts, Configuration conf, 
      String[] args) {
    opts = buildGeneralOptions(opts);
    CommandLineParser parser = new GnuParser();
    try {
      commandLine = parser.parse(opts, args, true);
      processGeneralOptions(conf, commandLine);
      return commandLine.getArgs();
    } catch(ParseException e) {
      LOG.warn("options parsing failed: "+e.getMessage());

      HelpFormatter formatter = new HelpFormatter();
      formatter.printHelp("general options are: ", opts);
    }
    return args;
}

 

其中,commandLine是GenericOptionsParser类的一个私有成员变量。

上面GenericOptionsParser类的成员方法parseGeneralOptions()可以作为解析Hadoop配置选项的一个高层的抽象方法了。

其中的buildGeneralOptions()接收Options opts然后又返回了opts,如下所示:

Java代码   收藏代码
  1.   /**  
  2.    * Specify properties of each generic option  
  3.    */   
  4. @SuppressWarnings ( "static-access" )  
  5. private  Options buildGeneralOptions(Options opts) {  
  6.     Option fs = OptionBuilder.withArgName("local|namenode:port" )  
  7.     .hasArg()  
  8.     .withDescription("specify a namenode" )  
  9.     .create("fs" );  
  10.     Option jt = OptionBuilder.withArgName("local|jobtracker:port" )  
  11.     .hasArg()  
  12.     .withDescription("specify a job tracker" )  
  13.     .create("jt" );  
  14.     Option oconf = OptionBuilder.withArgName("configuration file" )  
  15.     .hasArg()  
  16.     .withDescription("specify an application configuration file" )  
  17.     .create("conf" );  
  18.     Option property = OptionBuilder.withArgName("property=value" )  
  19.     .hasArgs()  
  20.     .withArgPattern("="1 )  
  21.     .withDescription("use value for given property" )  
  22.     .create('D' );  
  23.   
  24.     opts.addOption(fs);  
  25.     opts.addOption(jt);  
  26.     opts.addOption(oconf);  
  27.     opts.addOption(property);  
  28.       
  29.     return  opts;  
  30. }  
 /**
   * Specify properties of each generic option
   */
@SuppressWarnings("static-access")
private Options buildGeneralOptions(Options opts) {
    Option fs = OptionBuilder.withArgName("local|namenode:port")
    .hasArg()
    .withDescription("specify a namenode")
    .create("fs");
    Option jt = OptionBuilder.withArgName("local|jobtracker:port")
    .hasArg()
    .withDescription("specify a job tracker")
    .create("jt");
    Option oconf = OptionBuilder.withArgName("configuration file")
    .hasArg()
    .withDescription("specify an application configuration file")
    .create("conf");
    Option property = OptionBuilder.withArgName("property=value")
    .hasArgs()
    .withArgPattern("=", 1)
    .withDescription("use value for given property")
    .create('D');

    opts.addOption(fs);
    opts.addOption(jt);
    opts.addOption(oconf);
    opts.addOption(property);
    
    return opts;
}

 

这里说明一下Option类及其如何设置一个Option类的实例。

在buildGeneralOptions()方法接收Options opts然后又返回了opts,在这个过程中已经改变了opts的值。如下所示:

Java代码   收藏代码
  1.    /**  
  2.    * Specify properties of each generic option  
  3.    */   
  4. @SuppressWarnings ( "static-access" )  
  5. private  Options buildGeneralOptions(Options opts) {  
  6.     Option fs = OptionBuilder.withArgName("local|namenode:port" )  
  7.     .hasArg()  
  8.     .withDescription("specify a namenode" )  
  9.     .create("fs" );  
  10.     Option jt = OptionBuilder.withArgName("local|jobtracker:port" )  
  11.     .hasArg()  
  12.     .withDescription("specify a job tracker" )  
  13.     .create("jt" );  
  14.     Option oconf = OptionBuilder.withArgName("configuration file" )  
  15.     .hasArg()  
  16.     .withDescription("specify an application configuration file" )  
  17.     .create("conf" );  
  18.     Option property = OptionBuilder.withArgName("property=value" )  
  19.     .hasArgs()  
  20.     .withArgPattern("="1 )  
  21.     .withDescription("use value for given property" )  
  22.     .create('D' );  
  23.   
  24.     opts.addOption(fs);  
  25.     opts.addOption(jt);  
  26.     opts.addOption(oconf);  
  27.     opts.addOption(property);  
  28.       
  29.     return  opts;  
  30. }  
  /**
   * Specify properties of each generic option
   */
@SuppressWarnings("static-access")
private Options buildGeneralOptions(Options opts) {
    Option fs = OptionBuilder.withArgName("local|namenode:port")
    .hasArg()
    .withDescription("specify a namenode")
    .create("fs");
    Option jt = OptionBuilder.withArgName("local|jobtracker:port")
    .hasArg()
    .withDescription("specify a job tracker")
    .create("jt");
    Option oconf = OptionBuilder.withArgName("configuration file")
    .hasArg()
    .withDescription("specify an application configuration file")
    .create("conf");
    Option property = OptionBuilder.withArgName("property=value")
    .hasArgs()
    .withArgPattern("=", 1)
    .withDescription("use value for given property")
    .create('D');

    opts.addOption(fs);
    opts.addOption(jt);
    opts.addOption(oconf);
    opts.addOption(property);
    
    return opts;
}

 

开始传进来一个opts,它并没有任何内容(是指Option类的对象,即一个选项),因为从开始实例化就没有配置过Options opts。但是,在上面代码的后面部分,已经为opts设置内容了,其实就是设置添加Option类的对象到Options中去。

看看具体都添加了一些什么信息。拿出一项来看看:

Java代码   收藏代码
  1.  Option fs = OptionBuilder.withArgName( "local|namenode:port" )  
  2. .hasArg()  
  3. .withDescription("specify a namenode" )  
  4. .create("fs" );  
  5.   
  6.  opts.addOption(fs);  
     Option fs = OptionBuilder.withArgName("local|namenode:port")
    .hasArg()
    .withDescription("specify a namenode")
    .create("fs");

     opts.addOption(fs);

 

Option代表了一个命令行,我们看一下Option类的定义:

Java代码   收藏代码
  1. package  org.apache.commons.cli;  
  2.   
  3. import  java.util.ArrayList;  
  4. import  java.util.regex.Pattern;  
  5.   
  6. public   class  Option {  
  7.   
  8.    // 参数值没有被指定时,用-1表示    
  9.     public   static   final   int  UNINITIALIZED = - 1 ;  
  10.   
  11.     // 参数值为无穷时使用-2表示   
  12.     public   static   final   int  UNLIMITED_VALUES = - 2 ;  
  13.   
  14.     //   标识一个Option的字符串名称   
  15.     private  String opt;  
  16.   
  17.     // Option使用长名称表示   
  18.     private  String longOpt;  
  19.   
  20.     // 表明一个Option是否有一个相关的参数   
  21.     private   boolean  hasArg;  
  22.   
  23.     // 表明一个Option的参数的名称    
  24.     private  String argName =  "arg" ;  
  25.   
  26.     // 一个Option的描述信息   
  27.     private  String description;  
  28.   
  29.     // 一个Option是否是必须指定的   
  30.     private   boolean  required;  
  31.   
  32.     // 是否一个Option的参数值是可选的   
  33.     private   boolean  optionalArg;  
  34.   
  35.     // 一个Option可以具有参数值的个数    
  36.      private   int  numberOfArgs = UNINITIALIZED;  
  37.   
  38.     // 一个Option的类型   
  39.     private  Object type;  
  40.   
  41.     // 参数值列表   
  42.     private  ArrayList values =  new  ArrayList();  
  43.   
  44.     // 指定用作分隔符的字符   
  45.     private   char  valuesep;  
  46.   
  47.     // 参数样式及其它出现的次数    
  48.     private  Pattern argPattern;  
  49.     private   int  limit;  
  50.   
  51.     /**  
  52.      * 构造一个Option.  
  53.      *  
  54.      * @param opt 标识一个Option的名称   
  55.      * @param description   一个Option的描述信息  
  56.      */   
  57.     public  Option(String opt, String description)  
  58.            throws  IllegalArgumentException  
  59.     {  
  60.         this (opt,  nullfalse , description);  // 调用其他构造方法   
  61.     }  
  62.   
  63.     // 另一种构造方法   
  64.     public  Option(String opt,  boolean  hasArg, String description)  
  65.            throws  IllegalArgumentException  
  66.     {  
  67.         this (opt,  null , hasArg, description);  
  68.     }  
  69.   
  70.     // 还是构造一个Option   
  71.     public  Option(String opt, String longOpt,  boolean  hasArg,   
  72.                   String description)  
  73.            throws  IllegalArgumentException  
  74.     {  
  75.         // 验证一个Option是合法的   
  76.         OptionValidator.validateOption(opt);  
  77.   
  78.         this .opt = opt;  
  79.         this .longOpt = longOpt;  
  80.   
  81.        // if hasArg is set then the number of arguments is 1   
  82.         if  (hasArg)  
  83.         {  
  84.             this .numberOfArgs =  1 ;  
  85.         }  
  86.   
  87.         this .hasArg = hasArg;  
  88.         this .description = description;  
  89.     }  
  90.   
  91.     // 返回Option的ID   
  92.     public   int  getId()  
  93.     {  
  94.         return  getKey().charAt( 0 );  
  95.     }  
  96.   
  97.    /**  
  98.      * Returns the 'unique' Option identifier.  
  99.      *   
  100.      * @return the 'unique' Option identifier  
  101.      */   
  102.     String getKey()  
  103.     {  
  104.         // if 'opt' is null, then it is a 'long' option   
  105.         if  (opt ==  null )  
  106.         {  
  107.             return   this .longOpt;  
  108.         }  
  109.   
  110.         return   this .opt;  
  111.     }  
  112.   
  113.     /**   
  114.      * 返回一个Option的name  
  115.      */   
  116.     public  String getOpt()  
  117.     {  
  118.         return   this .opt;  
  119.     }  
  120.   
  121.     /**   
  122.      * 返回一个Option的类型  
  123.      */   
  124.     public  Object getType()  
  125.     {  
  126.         return   this .type;  
  127.     }  
  128.   
  129.    /**   
  130.      * 设置一个Option的类型  
  131.      */   
  132.     public   void  setType(Object type)  
  133.     {  
  134.         this .type = type;  
  135.     }  
  136.   
  137.     /**   
  138.      * 返回一个Option的长名称  
  139.      */   
  140.     public  String getLongOpt()  
  141.     {  
  142.         return   this .longOpt;  
  143.     }  
  144.   
  145.     /**   
  146.      * 设置一个Option的长名称  
  147.      */   
  148.     public   void  setLongOpt(String longOpt)  
  149.     {  
  150.         this .longOpt = longOpt;  
  151.     }  
  152.   
  153.     /**   
  154.      * 设置一个Option是否具有一个可选的参数  
  155.      */   
  156.     public   void  setOptionalArg( boolean  optionalArg)  
  157.     {  
  158.         this .optionalArg = optionalArg;  
  159.     }  
  160.   
  161.     /**   
  162.      * 返回一个Option的是否具有可选的参数  
  163.      */   
  164.     public   boolean  hasOptionalArg()  
  165.     {  
  166.         return   this .optionalArg;  
  167.     }  
  168.   
  169.     /**   
  170.      * 是否Option具有一个长名称  
  171.      */   
  172.     public   boolean  hasLongOpt()  
  173.     {  
  174.         return  ( this .longOpt !=  null );  
  175.     }  
  176.   
  177.     /**   
  178.      * 是否一个Option有一个必需的参数  
  179.      */   
  180.     public   boolean  hasArg()  
  181.     {  
  182.         return  ( this .numberOfArgs >  0 ) || (numberOfArgs == UNLIMITED_VALUES);  
  183.     }  
  184.       // 返回一个Option的描述信息   
  185.     public  String getDescription()  
  186.     {  
  187.         return   this .description;  
  188.     }  
  189.   
  190.       // 设置一个Option的描述信息   
  191.     public   void  setDescription(String description)  
  192.     {  
  193.         this .description = description;  
  194.     }  
  195.   
  196.     // 是否一个Option需要指定一个参数   
  197.     public   boolean  isRequired()  
  198.     {  
  199.         return   this .required;  
  200.     }  
  201.   
  202.     // 设置一个Option的参数是否必需   
  203.     public   void  setRequired( boolean  required)  
  204.     {  
  205.         this .required = required;  
  206.     }  
  207.   
  208.     // 设置这个参数值的显示名称   
  209.     public   void  setArgName(String argName)  
  210.     {  
  211.         this .argName = argName;  
  212.     }  
  213.   
  214.      // 返回这个参数值的显示名称   
  215.     public  String getArgName()  
  216.     {  
  217.         return   this .argName;  
  218.     }  
  219.   
  220.      //   是否这个参数值的显示名称已经被设置了   
  221.       public   boolean  hasArgName()  
  222.     {  
  223.         return  ( this .argName !=  null  &&  this .argName.length() >  0 );  
  224.     }  
  225.   
  226.      //   是否一个Option可以具有多个参数值   
  227.     public   boolean  hasArgs()  
  228.     {  
  229.         return  ( this .numberOfArgs >  1 )   
  230.                 || (this .numberOfArgs == UNLIMITED_VALUES);  
  231.     }  
  232.   
  233.      // 设置一个Option具有的参数值的个数   
  234.     public   void  setArgs( int  num)  
  235.     {  
  236.         this .numberOfArgs = num;  
  237.     }  
  238.   
  239.      // 设置值的分隔符字符   
  240.     public   void  setValueSeparator( char  sep)  
  241.     {  
  242.         this .valuesep = sep;  
  243.     }  
  244.   
  245.      // 返回值的分隔符字符   
  246.     public   char  getValueSeparator()  
  247.     {  
  248.         return   this .valuesep;  
  249.     }  
  250.   
  251.      //   是否一个Option指定了值的分隔符字符   
  252.     public   boolean  hasValueSeparator()  
  253.     {  
  254.         return  ( this .valuesep >  0 );  
  255.     }  
  256.   
  257.      //   一个Option是否指定多了参数的样式   
  258.     public   boolean  hasArgPattern()  
  259.     {  
  260.         return  (limit!= 0 &&argPattern!= null );  
  261.     }  
  262.   
  263.     public   void  setArgPattern( String argPattern,  int  limit )  
  264.     {  
  265.         if (argPattern== null  || argPattern.length()== 0  || limit== 0  )  
  266.           return ;  
  267.         this .argPattern = Pattern.compile(argPattern);  
  268.         this .limit = limit;  
  269.     }  
  270.   
  271.      //   返回一个Option具有参数的个数   
  272.     public   int  getArgs()  
  273.     {  
  274.         return   this .numberOfArgs;  
  275.     }  
  276.   
  277.      // 设置一个Option的值   
  278.     void  addValue(String value)  
  279.     {  
  280.         switch  (numberOfArgs)  
  281.         {  
  282.         case  UNINITIALIZED:  
  283.             throw   new  RuntimeException( "NO_ARGS_ALLOWED" );  
  284.   
  285.         default :  
  286.             processValue(value);  
  287.         }  
  288.     }  
  289.   
  290.      //   检查参数样式   
  291.     private   void  checkArgPattern( String arg ) {  
  292.       if (!hasArgPattern()) {  
  293.         add(arg);  
  294.       } else  {  
  295.         String [] tokens = argPattern.split(arg, -1 );  
  296.         if (tokens.length != limit+ 1 )  
  297.           throw   new  RuntimeException( "ARG_PATTERN_NOT_MATCH" );  
  298.         for ( int  i= 0 ; i<= limit; i++) {  
  299.           add(tokens[i]);  
  300.         }  
  301.       }  
  302.     }  
  303.   
  304.      // 处理一个Option的值   
  305.     private   void  processValue(String value)  
  306.     {  
  307.         // this Option has a separator character   
  308.         if  (hasValueSeparator())  
  309.         {  
  310.             // get the separator character   
  311.             char  sep = getValueSeparator();  
  312.   
  313.             // store the index for the value separator   
  314.             int  index = value.indexOf(sep);  
  315.   
  316.             // while there are more value separators   
  317.             while  (index != - 1 )  
  318.             {  
  319.                 // next value to be added    
  320.                 if  (values.size()/(limit+ 1 ) == (numberOfArgs -  1 ))  
  321.                 {  
  322.                     break ;  
  323.                 }  
  324.   
  325.   
  326.                 // store   
  327.                 checkArgPattern(value.substring(0 , index));  
  328.   
  329.   
  330.                 // parse   
  331.                 value = value.substring(index + 1 );  
  332.   
  333.   
  334.                 // get new index   
  335.                 index = value.indexOf(sep);  
  336.             }  
  337.         }  
  338.   
  339.   
  340.         // check if the argment matches specified pattern; if yes,   
  341.         // store the actual value or the last value that has been parsed   
  342.         checkArgPattern(value);  
  343.     }  
  344.   
  345.     //   向一个Option添加值,如果参数的个数大于0并且有足够的列表的时候才可以添加   
  346.       private   void  add(String value)  
  347.     {  
  348.         if  ((numberOfArgs >  0 ) && (values.size() > (numberOfArgs -  1 )))  
  349.         {  
  350.             throw   new  RuntimeException( "Cannot add value, list full." );  
  351.         }  
  352.   
  353.   
  354.         this .values.add(value);  
  355.     }  
  356.   
  357.      //   返回Option的值   
  358.     public  String getValue()  
  359.     {  
  360.         return  hasNoValues() ?  null  : (String)  this .values.get( 0 );  
  361.     }  
  362.   
  363.      // 返回指定的Option的值   
  364.     public  String getValue( int  index)  
  365.         throws  IndexOutOfBoundsException  
  366.     {  
  367.         return  hasNoValues() ?  null  : (String)  this .values.get(index);  
  368.     }  
  369.   
  370.      // 返回一个Option的值,或者第一个值,如果它没有值就返回一个默认值        
  371.     public  String getValue(String defaultValue)  
  372.     {  
  373.         String value = getValue();  
  374.   
  375.         return  (value !=  null ) ? value : defaultValue;  
  376.     }  
  377.   
  378.      //   以一个字符串数组的形式返回一个Option的所有值   
  379.     public  String[] getValues()  
  380.     {  
  381.         return  hasNoValues()  
  382.                ? null  : (String[])  this .values.toArray( new  String[] { });  
  383.     }  
  384.   
  385.      //   以列表的形式返回一个Option的所有值   
  386.     public  java.util.List getValuesList()  
  387.     {  
  388.         return   this .values;  
  389.     }  
  390.   
  391.      //   用于调试使用的   
  392.     public  String toString()  
  393.     {  
  394.         StringBuffer buf = new  StringBuffer().append( "[ option: " );  
  395.   
  396.         buf.append(this .opt);  
  397.   
  398.         if  ( this .longOpt !=  null )  
  399.         {  
  400.             buf.append(" " ).append( this .longOpt);  
  401.         }  
  402.   
  403.         buf.append(" " );  
  404.   
  405.         if  (hasArg)  
  406.         {  
  407.             buf.append("+ARG" );  
  408.         }  
  409.   
  410.         buf.append(" :: " ).append( this .description);  
  411.   
  412.         if  ( this .type !=  null )  
  413.         {  
  414.             buf.append(" :: " ).append( this .type);  
  415.         }  
  416.   
  417.         buf.append(" ]" );  
  418.   
  419.         return  buf.toString();  
  420.     }  
  421.   
  422.      //   一个Option是否可以是任意值   
  423.     private   boolean  hasNoValues()  
  424.     {  
  425.         return   this .values.size() ==  0 ;  
  426.     }  
  427. }  
package org.apache.commons.cli;

import java.util.ArrayList;
import java.util.regex.Pattern;

public class Option {

   // 参数值没有被指定时,用-1表示 
    public static final int UNINITIALIZED = -1;

    // 参数值为无穷时使用-2表示
    public static final int UNLIMITED_VALUES = -2;

    //   标识一个Option的字符串名称
    private String opt;

    // Option使用长名称表示
    private String longOpt;

    // 表明一个Option是否有一个相关的参数
    private boolean hasArg;

    // 表明一个Option的参数的名称 
    private String argName = "arg";

    // 一个Option的描述信息
    private String description;

    // 一个Option是否是必须指定的
    private boolean required;

    // 是否一个Option的参数值是可选的
    private boolean optionalArg;

    // 一个Option可以具有参数值的个数 
     private int numberOfArgs = UNINITIALIZED;

    // 一个Option的类型
    private Object type;

    // 参数值列表
    private ArrayList values = new ArrayList();

    // 指定用作分隔符的字符
    private char valuesep;

    // 参数样式及其它出现的次数 
    private Pattern argPattern;
    private int limit;

    /**
     * 构造一个Option.
     *
     * @param opt 标识一个Option的名称 
     * @param description   一个Option的描述信息
     */
    public Option(String opt, String description)
           throws IllegalArgumentException
    {
        this(opt, null, false, description); // 调用其他构造方法
    }

    // 另一种构造方法
    public Option(String opt, boolean hasArg, String description)
           throws IllegalArgumentException
    {
        this(opt, null, hasArg, description);
    }

    // 还是构造一个Option
    public Option(String opt, String longOpt, boolean hasArg, 
                  String description)
           throws IllegalArgumentException
    {
        // 验证一个Option是合法的
        OptionValidator.validateOption(opt);

        this.opt = opt;
        this.longOpt = longOpt;

       // if hasArg is set then the number of arguments is 1
        if (hasArg)
        {
            this.numberOfArgs = 1;
        }

        this.hasArg = hasArg;
        this.description = description;
    }

    // 返回Option的ID
    public int getId()
    {
        return getKey().charAt(0);
    }

   /**
     * Returns the 'unique' Option identifier.
     * 
     * @return the 'unique' Option identifier
     */
    String getKey()
    {
        // if 'opt' is null, then it is a 'long' option
        if (opt == null)
        {
            return this.longOpt;
        }

        return this.opt;
    }

    /** 
     * 返回一个Option的name
     */
    public String getOpt()
    {
        return this.opt;
    }

    /** 
     * 返回一个Option的类型
     */
    public Object getType()
    {
        return this.type;
    }

   /** 
     * 设置一个Option的类型
     */
    public void setType(Object type)
    {
        this.type = type;
    }

    /** 
     * 返回一个Option的长名称
     */
    public String getLongOpt()
    {
        return this.longOpt;
    }

    /** 
     * 设置一个Option的长名称
     */
    public void setLongOpt(String longOpt)
    {
        this.longOpt = longOpt;
    }

    /** 
     * 设置一个Option是否具有一个可选的参数
     */
    public void setOptionalArg(boolean optionalArg)
    {
        this.optionalArg = optionalArg;
    }

    /** 
     * 返回一个Option的是否具有可选的参数
     */
    public boolean hasOptionalArg()
    {
        return this.optionalArg;
    }

    /** 
     * 是否Option具有一个长名称
     */
    public boolean hasLongOpt()
    {
        return (this.longOpt != null);
    }

    /** 
     * 是否一个Option有一个必需的参数
     */
    public boolean hasArg()
    {
        return (this.numberOfArgs > 0) || (numberOfArgs == UNLIMITED_VALUES);
    }
      // 返回一个Option的描述信息
    public String getDescription()
    {
        return this.description;
    }

      // 设置一个Option的描述信息
    public void setDescription(String description)
    {
        this.description = description;
    }

    // 是否一个Option需要指定一个参数
    public boolean isRequired()
    {
        return this.required;
    }

    // 设置一个Option的参数是否必需
    public void setRequired(boolean required)
    {
        this.required = required;
    }

    // 设置这个参数值的显示名称
    public void setArgName(String argName)
    {
        this.argName = argName;
    }

     // 返回这个参数值的显示名称
    public String getArgName()
    {
        return this.argName;
    }

     //   是否这个参数值的显示名称已经被设置了
      public boolean hasArgName()
    {
        return (this.argName != null && this.argName.length() > 0);
    }

     //   是否一个Option可以具有多个参数值
    public boolean hasArgs()
    {
        return (this.numberOfArgs > 1) 
                || (this.numberOfArgs == UNLIMITED_VALUES);
    }

     // 设置一个Option具有的参数值的个数
    public void setArgs(int num)
    {
        this.numberOfArgs = num;
    }

     // 设置值的分隔符字符
    public void setValueSeparator(char sep)
    {
        this.valuesep = sep;
    }

     // 返回值的分隔符字符
    public char getValueSeparator()
    {
        return this.valuesep;
    }

     //   是否一个Option指定了值的分隔符字符
    public boolean hasValueSeparator()
    {
        return (this.valuesep > 0);
    }

     //   一个Option是否指定多了参数的样式
    public boolean hasArgPattern()
    {
        return (limit!=0&&argPattern!=null);
    }

    public void setArgPattern( String argPattern, int limit )
    {
        if(argPattern==null || argPattern.length()==0 || limit==0 )
          return;
        this.argPattern = Pattern.compile(argPattern);
        this.limit = limit;
    }

     //   返回一个Option具有参数的个数
    public int getArgs()
    {
        return this.numberOfArgs;
    }

     // 设置一个Option的值
    void addValue(String value)
    {
        switch (numberOfArgs)
        {
        case UNINITIALIZED:
            throw new RuntimeException("NO_ARGS_ALLOWED");

        default:
            processValue(value);
        }
    }

     //   检查参数样式
    private void checkArgPattern( String arg ) {
      if(!hasArgPattern()) {
        add(arg);
      } else {
        String [] tokens = argPattern.split(arg, -1);
        if(tokens.length != limit+1)
          throw new RuntimeException("ARG_PATTERN_NOT_MATCH");
        for(int i=0; i<= limit; i++) {
          add(tokens[i]);
        }
      }
    }

     // 处理一个Option的值
    private void processValue(String value)
    {
        // this Option has a separator character
        if (hasValueSeparator())
        {
            // get the separator character
            char sep = getValueSeparator();

            // store the index for the value separator
            int index = value.indexOf(sep);

            // while there are more value separators
            while (index != -1)
            {
                // next value to be added 
                if (values.size()/(limit+1) == (numberOfArgs - 1))
                {
                    break;
                }


                // store
                checkArgPattern(value.substring(0, index));


                // parse
                value = value.substring(index + 1);


                // get new index
                index = value.indexOf(sep);
            }
        }


        // check if the argment matches specified pattern; if yes,
        // store the actual value or the last value that has been parsed
        checkArgPattern(value);
    }

    //   向一个Option添加值,如果参数的个数大于0并且有足够的列表的时候才可以添加
      private void add(String value)
    {
        if ((numberOfArgs > 0) && (values.size() > (numberOfArgs - 1)))
        {
            throw new RuntimeException("Cannot add value, list full.");
        }


        this.values.add(value);
    }

     //   返回Option的值
    public String getValue()
    {
        return hasNoValues() ? null : (String) this.values.get(0);
    }

     // 返回指定的Option的值
    public String getValue(int index)
        throws IndexOutOfBoundsException
    {
        return hasNoValues() ? null : (String) this.values.get(index);
    }

     // 返回一个Option的值,或者第一个值,如果它没有值就返回一个默认值     
    public String getValue(String defaultValue)
    {
        String value = getValue();

        return (value != null) ? value : defaultValue;
    }

     //   以一个字符串数组的形式返回一个Option的所有值
    public String[] getValues()
    {
        return hasNoValues()
               ? null : (String[]) this.values.toArray(new String[] { });
    }

     //   以列表的形式返回一个Option的所有值
    public java.util.List getValuesList()
    {
        return this.values;
    }

     //   用于调试使用的
    public String toString()
    {
        StringBuffer buf = new StringBuffer().append("[ option: ");

        buf.append(this.opt);

        if (this.longOpt != null)
        {
            buf.append(" ").append(this.longOpt);
        }

        buf.append(" ");

        if (hasArg)
        {
            buf.append("+ARG");
        }

        buf.append(" :: ").append(this.description);

        if (this.type != null)
        {
            buf.append(" :: ").append(this.type);
        }

        buf.append(" ]");

        return buf.toString();
    }

     //   一个Option是否可以是任意值
    private boolean hasNoValues()
    {
        return this.values.size() == 0;
    }
}

 

可以看出,一个Option所具有的信息很多:长名称(longOpt)、短名称(name)、类型(type)、样式(pattern)、参数个数(numberOfArgs)、参数值的字符分隔符、ID,描述等等。

只有设置好了这些Option的信息,调用private Options buildGeneralOptions(Options opts) 方法时候返回的Options可以被后面进行解析使用。

继续向下看:

Java代码   收藏代码
  1.  Option fs = OptionBuilder.withArgName( "local|namenode:port" )  
  2. .hasArg()  
  3. .withDescription("specify a namenode" )  
  4. .create("fs" );  
  5.   
  6.  opts.addOption(fs);  
     Option fs = OptionBuilder.withArgName("local|namenode:port")
    .hasArg()
    .withDescription("specify a namenode")
    .create("fs");

     opts.addOption(fs);

 

有一个很重要的类OptionBuilder,它才完成了“充实”一个Option的过程,然后经过多次调用,会将多个Option都添加到opts列表中。

看一看OptionBuilder类的withArgName()方法:

Java代码   收藏代码
  1.   /**  
  2.  * The next Option created will have the specified argument value   
  3.  * name.  
  4.  *  
  5.  * @param name the name for the argument value  
  6.  * @return the OptionBuilder instance  
  7.  */   
  8. public   static  OptionBuilder withArgName(String name)  
  9. {  
  10.     OptionBuilder.argName = name;  
  11.   
  12.     return  instance;  
  13. }  
     /**
     * The next Option created will have the specified argument value 
     * name.
     *
     * @param name the name for the argument value
     * @return the OptionBuilder instance
     */
    public static OptionBuilder withArgName(String name)
    {
        OptionBuilder.argName = name;

        return instance;
    }

 

上面,为一个OptionBuilder的实例指定一个参数(argName)为name,实际上是返回了一个具有name的OptionBuilder实例。

然后,又调用了hasArg()方法,它也是OptionBuilder类的静态方法:

Java代码   收藏代码
  1.   /**  
  2.  * The next Option created will require an argument value.  
  3.  *  
  4.  * @return the OptionBuilder instance  
  5.  */   
  6. public   static  OptionBuilder hasArg()  
  7. {  
  8.     OptionBuilder.numberOfArgs = 1 ;  
  9.   
  10.     return  instance;  
  11. }  
     /**
     * The next Option created will require an argument value.
     *
     * @return the OptionBuilder instance
     */
    public static OptionBuilder hasArg()
    {
        OptionBuilder.numberOfArgs = 1;

        return instance;
    }

 

为刚才指定参数名的那个OptionBuilder实例设置了参数的个数,因为第一次设置,当然个数为1了。

调用withDescription()方法来设定描述信息:

Java代码   收藏代码
  1.   /**  
  2.  * The next Option created will have the specified description  
  3.  *  
  4.  * @param newDescription a description of the Option's purpose  
  5.  * @return the OptionBuilder instance  
  6.  */   
  7. public   static  OptionBuilder withDescription(String newDescription)  
  8. {  
  9.     OptionBuilder.description = newDescription;  
  10.   
  11.     return  instance;  
  12. }  
     /**
     * The next Option created will have the specified description
     *
     * @param newDescription a description of the Option's purpose
     * @return the OptionBuilder instance
     */
    public static OptionBuilder withDescription(String newDescription)
    {
        OptionBuilder.description = newDescription;

        return instance;
    }

 

比较关键的是最后一步调用,通过调用OptionBuilder类的create()方法才真正完成了一个Option的创建:

Java代码   收藏代码
  1.   /**  
  2.  * Create an Option using the current settings and with   
  3.  * the specified Option <code>char</code>.  
  4.  *  
  5.  * @param opt the <code>java.lang.String</code> representation   
  6.  * of the Option  
  7.  * @return the Option instance  
  8.  * @throws IllegalArgumentException if <code>opt</code> is not  
  9.  * a valid character. See Option.  
  10.  */   
  11. public   static  Option create(String opt)  
  12.                      throws  IllegalArgumentException  
  13. {  
  14.     // create the option   
  15.     Option option = new  Option(opt, description);  
  16.   
  17.   
  18.     // set the option properties   
  19.     option.setLongOpt(longopt);  
  20.     option.setRequired(required);  
  21.     option.setOptionalArg(optionalArg);  
  22.     option.setArgs(numberOfArgs);  
  23.     option.setType(type);  
  24.     option.setValueSeparator(valuesep);  
  25.     option.setArgName(argName);  
  26.     option.setArgPattern(argPattern, limit);  
  27.   
  28.   
  29.     // reset the OptionBuilder properties   
  30.     OptionBuilder.reset();  
  31.   
  32.     // return the Option instance   
  33.     return  option;  
  34. }  
     /**
     * Create an Option using the current settings and with 
     * the specified Option <code>char</code>.
     *
     * @param opt the <code>java.lang.String</code> representation 
     * of the Option
     * @return the Option instance
     * @throws IllegalArgumentException if <code>opt</code> is not
     * a valid character. See Option.
     */
    public static Option create(String opt)
                         throws IllegalArgumentException
    {
        // create the option
        Option option = new Option(opt, description);


        // set the option properties
        option.setLongOpt(longopt);
        option.setRequired(required);
        option.setOptionalArg(optionalArg);
        option.setArgs(numberOfArgs);
        option.setType(type);
        option.setValueSeparator(valuesep);
        option.setArgName(argName);
        option.setArgPattern(argPattern, limit);


        // reset the OptionBuilder properties
        OptionBuilder.reset();

        // return the Option instance
        return option;
    }

 

从上面一个Option的设置,我们可以看出来,OptionBuilder类其实是一个辅助工具,用来收集与一个Option相关的信息,从而将这些信息一次全部赋予到一个新建的Option对象上,这个对象现在具有详细的信息了。

接着,通过CommandLineParser parser的parse方法,可以知道public abstract class Parser implements CommandLineParser,从抽象类Parser中找到parse的实现:

Java代码   收藏代码
  1. public  CommandLine parse(Options options, String[] arguments,   
  2.                         boolean  stopAtNonOption)  
  3.    throws  ParseException  
  4.   
  5.    return  parse(options, arguments,  null , stopAtNonOption);  
     public CommandLine parse(Options options, String[] arguments, 
                             boolean stopAtNonOption)
        throws ParseException
    {
        return parse(options, arguments, null, stopAtNonOption);
    }

 

参数stopAtNonOption表明,如果解析过程中遇到的是一个空选项是否仍然继续解析。从前面parseGeneralOptions方法 中commandLine = parser.parse(opts, args, true);可知:我们传递过来一个true。

再次调用Parser类的重载成员方法parse(),如下所示,解析过程非常详细:

Java代码   收藏代码
  1.   /**  
  2.  * Parse the arguments according to the specified options and  
  3.  * properties.  
  4.  *  
  5.  * @param options the specified Options  
  6.  * @param arguments the command line arguments  
  7.  * @param properties command line option name-value pairs  
  8.  * @param stopAtNonOption stop parsing the arguments when the first  
  9.  * non option is encountered.  
  10.  *  
  11.  * @return the list of atomic option and value tokens  
  12.  *  
  13.  * @throws ParseException if there are any problems encountered  
  14.  * while parsing the command line tokens.  
  15.  */   
  16. public  CommandLine parse(Options options, String[] arguments,   
  17.                          Properties properties, boolean  stopAtNonOption)  
  18.     throws  ParseException  
  19. {  
  20.     // initialise members   
  21.     this .options = options;  
  22.     requiredOptions = options.getRequiredOptions();  
  23.     cmd = new  CommandLine();  
  24.   
  25.     boolean  eatTheRest =  false ;  
  26.   
  27.     if  (arguments ==  null )  
  28.     {  
  29.         arguments = new  String[ 0 ];  
  30.     }  
  31.   
  32.     List tokenList = Arrays.asList(flatten(this .options,   
  33.                                            arguments,   
  34.                                            stopAtNonOption));  
  35.   
  36.     ListIterator iterator = tokenList.listIterator();  
  37.   
  38.    // process each flattened token   
  39.     while  (iterator.hasNext())  
  40.     {  
  41.         String t = (String) iterator.next();  
  42.   
  43.         // the value is the double-dash   
  44.         if  ( "--" .equals(t))  
  45.         {  
  46.             eatTheRest = true ;  
  47.         }  
  48.   
  49.         // the value is a single dash   
  50.         else   if  ( "-" .equals(t))  
  51.         {  
  52.             if  (stopAtNonOption)  
  53.             {  
  54.                 eatTheRest = true ;  
  55.             }  
  56.             else   
  57.             {  
  58.                 cmd.addArg(t);  
  59.             }  
  60.         }  
  61.   
  62.         // the value is an option   
  63.         else   if  (t.startsWith( "-" ))  
  64.         {  
  65.             if  (stopAtNonOption && !options.hasOption(t))  
  66.             {  
  67.                 eatTheRest = true ;  
  68.                 cmd.addArg(t);  
  69.             }  
  70.             else   
  71.             {  
  72.                 processOption(t, iterator);  
  73.             }  
  74.         }  
  75.   
  76.         // the value is an argument   
  77.         else   
  78.         {  
  79.             cmd.addArg(t);  
  80.   
  81.             if  (stopAtNonOption)  
  82.             {  
  83.                 eatTheRest = true ;  
  84.             }  
  85.         }  
  86.   
  87.         // eat the remaining tokens   
  88.         if  (eatTheRest)  
  89.         {  
  90.             while  (iterator.hasNext())  
  91.             {  
  92.                 String str = (String) iterator.next();  
  93.   
  94.                 // ensure only one double-dash is added   
  95.                 if  (! "--" .equals(str))  
  96.                 {  
  97.                     cmd.addArg(str);  
  98.                 }  
  99.             }  
  100.         }  
  101.     }  
  102.   
  103.     processProperties(properties);  
  104.     checkRequiredOptions();  
  105.   
  106.     return  cmd;  
  107. }  
     /**
     * Parse the arguments according to the specified options and
     * properties.
     *
     * @param options the specified Options
     * @param arguments the command line arguments
     * @param properties command line option name-value pairs
     * @param stopAtNonOption stop parsing the arguments when the first
     * non option is encountered.
     *
     * @return the list of atomic option and value tokens
     *
     * @throws ParseException if there are any problems encountered
     * while parsing the command line tokens.
     */
    public CommandLine parse(Options options, String[] arguments, 
                             Properties properties, boolean stopAtNonOption)
        throws ParseException
    {
        // initialise members
        this.options = options;
        requiredOptions = options.getRequiredOptions();
        cmd = new CommandLine();

        boolean eatTheRest = false;

        if (arguments == null)
        {
            arguments = new String[0];
        }

        List tokenList = Arrays.asList(flatten(this.options, 
                                               arguments, 
                                               stopAtNonOption));

        ListIterator iterator = tokenList.listIterator();

       // process each flattened token
        while (iterator.hasNext())
        {
            String t = (String) iterator.next();

            // the value is the double-dash
            if ("--".equals(t))
            {
                eatTheRest = true;
            }

            // the value is a single dash
            else if ("-".equals(t))
            {
                if (stopAtNonOption)
                {
                    eatTheRest = true;
                }
                else
                {
                    cmd.addArg(t);
                }
            }

            // the value is an option
            else if (t.startsWith("-"))
            {
                if (stopAtNonOption && !options.hasOption(t))
                {
                    eatTheRest = true;
                    cmd.addArg(t);
                }
                else
                {
                    processOption(t, iterator);
                }
            }

            // the value is an argument
            else
            {
                cmd.addArg(t);

                if (stopAtNonOption)
                {
                    eatTheRest = true;
                }
            }

            // eat the remaining tokens
            if (eatTheRest)
            {
                while (iterator.hasNext())
                {
                    String str = (String) iterator.next();

                    // ensure only one double-dash is added
                    if (!"--".equals(str))
                    {
                        cmd.addArg(str);
                    }
                }
            }
        }

        processProperties(properties);
        checkRequiredOptions();

        return cmd;
    }

 

解析之后,返回CommandLine类的实例,从而GenericOptionsParser类的成员变量commandLine获取到了一个引用。commandLine是GenericOptionsParser类的一个私有成员变量。

看一下CommandLine类的实现:

Java代码   收藏代码
  1. package  org.apache.commons.cli;  
  2.   
  3. import  java.util.Collection;  
  4. import  java.util.HashMap;  
  5. import  java.util.Iterator;  
  6. import  java.util.LinkedList;  
  7. import  java.util.List;  
  8. import  java.util.Map;  
  9.   
  10. /**   
  11. * Represents list of arguments parsed against  
  12. * a {@link Options} descriptor.  
  13. *  
  14. * It allows querying of a boolean {@link #hasOption(String opt)},  
  15. * in addition to retrieving the {@link #getOptionValue(String opt)}  
  16. * for options requiring arguments.  
  17. */   
  18. public   class  CommandLine {  
  19.   
  20.     // 不能识别的 options/arguments    
  21.     private  List args =  new  LinkedList();  
  22.   
  23.     /** the processed options */   
  24.     private  Map options =  new  HashMap();  
  25.   
  26.     /** the option name map */   
  27.     private  Map names =  new  HashMap();  
  28.   
  29.     /** Map of unique options for ease to get complete list of options */   
  30.     private  Map hashcodeMap =  new  HashMap();  
  31.   
  32.     /** the processed options */   
  33.     private  Option[] optionsArray;  
  34.   
  35.      // 创建一个命令行CommandLine的实例。   
  36.     CommandLine()  
  37.     {  
  38.         // nothing to do   
  39.     }  
  40.   
  41.     // 从options这个HashMap中查看,判断是否opt已经被设置了   
  42.     public   boolean  hasOption(String opt)  
  43.     {  
  44.         return  options.containsKey(opt);  
  45.     }  
  46.   
  47.      // 调用hasOption()方法,从options这个HashMap中查看,判断是否opt已经被设置了   
  48.     public   boolean  hasOption( char  opt)  
  49.     {  
  50.         return  hasOption(String.valueOf(opt));  
  51.     }  
  52.   
  53.     // 根据String opt返回Option的Object类型   
  54.     public  Object getOptionObject(String opt)  
  55.     {  
  56.         String res = getOptionValue(opt);  
  57.   
  58.         if  (!options.containsKey(opt))  
  59.         {  
  60.             return   null ;  
  61.         }  
  62.   
  63.         Object type = ((Option) options.get(opt)).getType();  
  64.   
  65.         return  (res ==  null )        ?  null  : TypeHandler.createValue(res, type);  
  66.     }  
  67.   
  68.      // 根据char opt返回Option的Object类型   
  69.     public  Object getOptionObject( char  opt)  
  70.     {  
  71.         return  getOptionObject(String.valueOf(opt));  
  72.     }  
  73.   
  74.     // 根据指定的String opt获取Option的值   
  75.     public  String getOptionValue(String opt)  
  76.     {  
  77.         String[] values = getOptionValues(opt);  
  78.   
  79.         return  (values ==  null ) ?  null  : values[ 0 ];  
  80.     }  
  81.   
  82.      // 根据指定的char opt获取Option的值   
  83.     public  String getOptionValue( char  opt)  
  84.     {  
  85.         return  getOptionValue(String.valueOf(opt));  
  86.     }  
  87.   
  88.     /**   
  89.      * Retrieves the array of values, if any, of an option.  
  90.      *  
  91.      * @param opt string name of the option  
  92.      * @return Values of the argument if option is set, and has an argument,  
  93.      * otherwise null.  
  94.      */   
  95.     public  String[] getOptionValues(String opt)  
  96.     {  
  97.         opt = Util.stripLeadingHyphens(opt);  
  98.   
  99.         String key = opt;  
  100.   
  101.         if  (names.containsKey(opt))  
  102.         {  
  103.             key = (String) names.get(opt);  
  104.         }  
  105.   
  106.         if  (options.containsKey(key))  
  107.         {  
  108.             return  ((Option) options.get(key)).getValues();  
  109.         }  
  110.   
  111.         return   null ;  
  112.     }  
  113.   
  114.      // 根据指定的String opt,返回Option的值的一个数组   
  115.     public  String[] getOptionValues( char  opt)  
  116.     {  
  117.         return  getOptionValues(String.valueOf(opt));  
  118.     }  
  119.   
  120.      // 根据指定的String opt和String defaultValue获取Option的值   
  121.     public  String getOptionValue(String opt, String defaultValue)  
  122.     {  
  123.         String answer = getOptionValue(opt);  
  124.   
  125.         return  (answer !=  null ) ? answer : defaultValue;  
  126.     }  
  127.   
  128.      // 根据指定的char opt和String defaultValue获取Option的值   
  129.     public  String getOptionValue( char  opt, String defaultValue)  
  130.     {  
  131.         return  getOptionValue(String.valueOf(opt), defaultValue);  
  132.     }  
  133.   
  134.      // 返回不能够解析的Option和参数的一个数组   
  135.     public  String[] getArgs()  
  136.     {  
  137.         String[] answer = new  String[args.size()];  
  138.   
  139.         args.toArray(answer);  
  140.   
  141.         return  answer;  
  142.     }  
  143.   
  144.       // 返回不能够解析的Option和参数的一个列表   
  145.     public  List getArgList()  
  146.     {  
  147.         return  args;  
  148.     }  
  149.   
  150.     /**   
  151.      * jkeyes  
  152.      * - commented out until it is implemented properly  
  153.      * <p>Dump state, suitable for debugging.</p>  
  154.      *  
  155.      * @return Stringified form of this object  
  156.      */   
  157.     public  String toString() {  
  158.         StringBuffer buf = new  StringBuffer();  
  159.               
  160.         buf.append("[ CommandLine: [ options: " );  
  161.         buf.append(options.toString());  
  162.         buf.append(" ] [ args: " );  
  163.         buf.append(args.toString());  
  164.         buf.append(" ] ]" );  
  165.               
  166.         return  buf.toString();  
  167.     }  
  168.   
  169.     /**  
  170.      * Add left-over unrecognized option/argument.  
  171.      *  
  172.      * @param arg the unrecognised option/argument.  
  173.      */   
  174.     void  addArg(String arg)  
  175.     {  
  176.         args.add(arg);  
  177.     }  
  178.   
  179.    // 向CommandLine中添加一个Option,其中Option的值(可能多个)被存储   
  180.     void  addOption(Option opt)  
  181.     {  
  182.         hashcodeMap.put(new  Integer(opt.hashCode()), opt);  
  183.   
  184.         String key = opt.getKey();  
  185.   
  186.         if  (key ==  null )  
  187.         {  
  188.             key = opt.getLongOpt();  
  189.         }  
  190.         else   
  191.         {  
  192.             names.put(opt.getLongOpt(), key);  
  193.         }  
  194.   
  195.         options.put(key, opt);  
  196.     }  
  197.   
  198.      // 返回CommandLine的Option 成员表的一个迭代器   
  199.     public  Iterator iterator()  
  200.     {  
  201.         return  hashcodeMap.values().iterator();  
  202.     }  
  203.   
  204.     // 返回处理过的Option的对象数组   
  205.     public  Option[] getOptions()  
  206.     {  
  207.         Collection processed = options.values();  
  208.   
  209.   
  210.         // reinitialise array   
  211.         optionsArray = new  Option[processed.size()];  
  212.   
  213.         // return the array   
  214.         return  (Option[]) processed.toArray(optionsArray);  
  215.     }  
  216. }  
package org.apache.commons.cli;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/** 
* Represents list of arguments parsed against
* a {@link Options} descriptor.
*
* It allows querying of a boolean {@link #hasOption(String opt)},
* in addition to retrieving the {@link #getOptionValue(String opt)}
* for options requiring arguments.
*/
public class CommandLine {

    // 不能识别的 options/arguments 
    private List args = new LinkedList();

    /** the processed options */
    private Map options = new HashMap();

    /** the option name map */
    private Map names = new HashMap();

    /** Map of unique options for ease to get complete list of options */
    private Map hashcodeMap = new HashMap();

    /** the processed options */
    private Option[] optionsArray;

     // 创建一个命令行CommandLine的实例。
    CommandLine()
    {
        // nothing to do
    }

    // 从options这个HashMap中查看,判断是否opt已经被设置了
    public boolean hasOption(String opt)
    {
        return options.containsKey(opt);
    }

     // 调用hasOption()方法,从options这个HashMap中查看,判断是否opt已经被设置了
    public boolean hasOption(char opt)
    {
        return hasOption(String.valueOf(opt));
    }

    // 根据String opt返回Option的Object类型
    public Object getOptionObject(String opt)
    {
        String res = getOptionValue(opt);

        if (!options.containsKey(opt))
        {
            return null;
        }

        Object type = ((Option) options.get(opt)).getType();

        return (res == null)        ? null : TypeHandler.createValue(res, type);
    }

     // 根据char opt返回Option的Object类型
    public Object getOptionObject(char opt)
    {
        return getOptionObject(String.valueOf(opt));
    }

    // 根据指定的String opt获取Option的值
    public String getOptionValue(String opt)
    {
        String[] values = getOptionValues(opt);

        return (values == null) ? null : values[0];
    }

     // 根据指定的char opt获取Option的值
    public String getOptionValue(char opt)
    {
        return getOptionValue(String.valueOf(opt));
    }

    /** 
     * Retrieves the array of values, if any, of an option.
     *
     * @param opt string name of the option
     * @return Values of the argument if option is set, and has an argument,
     * otherwise null.
     */
    public String[] getOptionValues(String opt)
    {
        opt = Util.stripLeadingHyphens(opt);

        String key = opt;

        if (names.containsKey(opt))
        {
            key = (String) names.get(opt);
        }

        if (options.containsKey(key))
        {
            return ((Option) options.get(key)).getValues();
        }

        return null;
    }

     // 根据指定的String opt,返回Option的值的一个数组
    public String[] getOptionValues(char opt)
    {
        return getOptionValues(String.valueOf(opt));
    }

     // 根据指定的String opt和String defaultValue获取Option的值
    public String getOptionValue(String opt, String defaultValue)
    {
        String answer = getOptionValue(opt);

        return (answer != null) ? answer : defaultValue;
    }

     // 根据指定的char opt和String defaultValue获取Option的值
    public String getOptionValue(char opt, String defaultValue)
    {
        return getOptionValue(String.valueOf(opt), defaultValue);
    }

     // 返回不能够解析的Option和参数的一个数组
    public String[] getArgs()
    {
        String[] answer = new String[args.size()];

        args.toArray(answer);

        return answer;
    }

      // 返回不能够解析的Option和参数的一个列表
    public List getArgList()
    {
        return args;
    }

    /** 
     * jkeyes
     * - commented out until it is implemented properly
     * <p>Dump state, suitable for debugging.</p>
     *
     * @return Stringified form of this object
     */
    public String toString() {
        StringBuffer buf = new StringBuffer();
            
        buf.append("[ CommandLine: [ options: ");
        buf.append(options.toString());
        buf.append(" ] [ args: ");
        buf.append(args.toString());
        buf.append(" ] ]");
            
        return buf.toString();
    }

    /**
     * Add left-over unrecognized option/argument.
     *
     * @param arg the unrecognised option/argument.
     */
    void addArg(String arg)
    {
        args.add(arg);
    }

   // 向CommandLine中添加一个Option,其中Option的值(可能多个)被存储
    void addOption(Option opt)
    {
        hashcodeMap.put(new Integer(opt.hashCode()), opt);

        String key = opt.getKey();

        if (key == null)
        {
            key = opt.getLongOpt();
        }
        else
        {
            names.put(opt.getLongOpt(), key);
        }

        options.put(key, opt);
    }

     // 返回CommandLine的Option 成员表的一个迭代器
    public Iterator iterator()
    {
        return hashcodeMap.values().iterator();
    }

    // 返回处理过的Option的对象数组
    public Option[] getOptions()
    {
        Collection processed = options.values();


        // reinitialise array
        optionsArray = new Option[processed.size()];

        // return the array
        return (Option[]) processed.toArray(optionsArray);
    }
}

 

一个CommandLine中包含一个重要的HashMap,里面存储的是键值对,即(key, opt),通过它可以非常方便地设置和访问。

接着在parseGeneralOptions方法中调用processGeneralOptions()方法,进行处理:

Java代码   收藏代码
  1. processGeneralOptions(conf, commandLine);  
processGeneralOptions(conf, commandLine);

 

processGeneralOptions的处理过程如下:

Java代码   收藏代码
  1. /**  
  2.  * Modify configuration according user-specified generic options  
  3.  * @param conf Configuration to be modified  
  4.  * @param line User-specified generic options  
  5.  */   
  6.  private   void  processGeneralOptions(Configuration conf,  
  7.   CommandLine line) {  
  8. if  (line.hasOption( "fs" )) {  
  9.   conf.set("fs.default.name" , line.getOptionValue( "fs" ));  
  10. }  
  11.   
  12. if  (line.hasOption( "jt" )) {  
  13.   conf.set("mapred.job.tracker" , line.getOptionValue( "jt" ));  
  14. }  
  15. if  (line.hasOption( "conf" )) {  
  16.   conf.addResource(new  Path(line.getOptionValue( "conf" )));  
  17. }  
  18. if  (line.hasOption( 'D' )) {  
  19.   String[] property = line.getOptionValues('D' );  
  20.   for ( int  i= 0 ; i<property.length- 1 ; i=i+ 2 ) {  
  21.     if  (property[i]!= null )  
  22.       conf.set(property[i], property[i+1 ]);  
  23.   }  
  24. }  
    /**
     * Modify configuration according user-specified generic options
     * @param conf Configuration to be modified
     * @param line User-specified generic options
     */
     private void processGeneralOptions(Configuration conf,
      CommandLine line) {
    if (line.hasOption("fs")) {
      conf.set("fs.default.name", line.getOptionValue("fs"));
    }

    if (line.hasOption("jt")) {
      conf.set("mapred.job.tracker", line.getOptionValue("jt"));
    }
    if (line.hasOption("conf")) {
      conf.addResource(new Path(line.getOptionValue("conf")));
    }
    if (line.hasOption('D')) {
      String[] property = line.getOptionValues('D');
      for(int i=0; i<property.length-1; i=i+2) {
        if (property[i]!=null)
          conf.set(property[i], property[i+1]);
      }
    }
}

 

传进去一个CommandLine实例,通过CommanLine的信息,来设置Configuration conf对象。设置Configuration conf对象的目的是:为Hadoop的Tool工作而设置的,比如WordCount这个工具,在运行开始时需要获取到Hadoop的配置信息的,这个 就需要从这里设置的Configuration conf对象来获取。

上面这个processGeneralOptions()方法,是根据CommanLine的对象,获取到所有参数值的一个数组,并返回。

到此位置,前面都是为了初始化一个GenericOptionsParser parser解析器所做的工作:

Java代码   收藏代码
  1. GenericOptionsParser parser =  new  GenericOptionsParser(conf, args);  
GenericOptionsParser parser = new GenericOptionsParser(conf, args);

 

进而,可以使用 GenericOptionsParser类的实例parser 来获取Hadoop的通用参数了:

Java代码   收藏代码
  1. //get the args w/o generic hadoop args   
  2. tring[] toolArgs = parser.getRemainingArgs();  
     //get the args w/o generic hadoop args
    String[] toolArgs = parser.getRemainingArgs();

 

已经具备了运行Hadoop工具的条件了,可以启动了:

Java代码   收藏代码
  1. return  tool.run(toolArgs);  
  return tool.run(toolArgs);

 

可以根据返回的状态码检查工具运行情况。

上面Tool tool就是我们实例化的WordCount对象,这时候才进入到WordCount实现中。

前面终于把命令行和Hadoop的配置类说完了,其实就是为了获取Hadoop的配置信息,在这些配置存在的环境下才能进行Tool的运行工作。

 

众所周之,Hadoop实现了Google的MapReduce算法,所以对于一个Hadoop的Tool必须实现Map函数和Reduce函数了,分别在处理数据的工作中进行映射和化简。

那么WordCount这个工具自然也要实现Map和Reduce函数了。

要知道,在WordCount中,定义了两个成员变量,如下所示:

Java代码   收藏代码
  1. private   final   static  IntWritable one =  new  IntWritable( 1 );  
  2. rivate Text word = new  Text();  
     private final static IntWritable one = new IntWritable(1);
    private Text word = new Text();

 

IntWritable类是一个为整数可以进行写、可以进行比较而定义的,比如统计单词出现频率就是一个整数。

Text类是用来存储文本内容的,存储的文本内容经过了编码、解码等等操作。可以参考org.apache.hadoop.io.Text类来了解更多信息。

先看Map的实现,如下所示:

Java代码   收藏代码
  1. /**  
  2.    * MapClass是一个内部静态类。统计数据文件中每一行的单词。  
  3.    */   
  4. public   static   class  MapClass  extends  MapReduceBase  
  5.     implements  Mapper<LongWritable, Text, Text, IntWritable> {  
  6.       
  7.     private   final   static  IntWritable one =  new  IntWritable( 1 );  
  8.     private  Text word =  new  Text();  
  9.       
  10.     public   void  map(LongWritable key, Text value,   
  11.                     OutputCollector<Text, IntWritable> output,   
  12.                     Reporter reporter) throws  IOException {  
  13.       String line = value.toString();  
  14.       StringTokenizer itr = new  StringTokenizer(line);  
  15.       while  (itr.hasMoreTokens()) {  
  16.         word.set(itr.nextToken());  
  17.         output.collect(word, one);  
  18.       }  
  19.     }  
  20. }  
/**
   * MapClass是一个内部静态类。统计数据文件中每一行的单词。
   */
public static class MapClass extends MapReduceBase
    implements Mapper<LongWritable, Text, Text, IntWritable> {
    
    private final static IntWritable one = new IntWritable(1);
    private Text word = new Text();
    
    public void map(LongWritable key, Text value, 
                    OutputCollector<Text, IntWritable> output, 
                    Reporter reporter) throws IOException {
      String line = value.toString();
      StringTokenizer itr = new StringTokenizer(line);
      while (itr.hasMoreTokens()) {
        word.set(itr.nextToken());
        output.collect(word, one);
      }
    }
}

 

StringTokenizer是将String line = value.toString();这个从文本中获取到的可能很长的不规范(带空格或者其他分隔符,这里默认就是空格作为分隔符的)的字符串进行处理,提取由空格作为分隔符的每个单词。

然后,word.set(itr.nextToken());将提取出来的单词设置到Text word中,看一下org.apache.hadoop.io.Text类的set()方法,如下所示:

Java代码   收藏代码
  1.    public   void  set(String string) {  
  2.     try  {  
  3.       ByteBuffer bb = encode(string, true );  // 将传进来的单词string进行编码后放到字节缓冲区ByteBuffer bb中   
  4.       bytes = bb.array(); // bytes是一个字节数组,是Text的成员   
  5.       length = bb.limit(); // length是单词字符串转化为字节后的长度,length是Text的成员   
  6.     }catch (CharacterCodingException e) {  
  7.       throw   new  RuntimeException( "Should not have happened "  + e.toString());   
  8.     }  
  9. }  
  public void set(String string) {
    try {
      ByteBuffer bb = encode(string, true); // 将传进来的单词string进行编码后放到字节缓冲区ByteBuffer bb中
      bytes = bb.array(); // bytes是一个字节数组,是Text的成员
      length = bb.limit(); // length是单词字符串转化为字节后的长度,length是Text的成员
    }catch(CharacterCodingException e) {
      throw new RuntimeException("Should not have happened " + e.toString()); 
    }
}

 

上面map()方法中,OutputCollector<Text, IntWritable> output是一个输出收集器,因为执行一个Map任务需要输出中间结果的,以便于下一个步骤进行Reduce任务进行合并简化。

OutputCollector<Text, IntWritable>是一个接口,先看看这个接口的定义吧,非常简单:

Java代码   收藏代码
  1. package  org.apache.hadoop.mapred;  
  2.   
  3. import  java.io.IOException;  
  4.   
  5. import  org.apache.hadoop.io.Writable;  
  6. import  org.apache.hadoop.io.WritableComparable;  
  7.   
  8.   
  9. /**  
  10. * 收集<key, value>对。用于Map-Reduce框架,可以被Mapper或者Reducer使用    
  11. */   
  12. public   interface  OutputCollector<K  extends  WritableComparable,  
  13.                                  V extends  Writable> {  
  14.   
  15. /** Adds a key/value pair to the output.  
  16.    *  
  17.    * @param key the key to collect.  
  18.    * @param value to value to collect.  
  19.    * @throws IOException  
  20.    */   
  21. void  collect(K key, V value)  throws  IOException;  
  22. }  
package org.apache.hadoop.mapred;

import java.io.IOException;

import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;


/**
* 收集<key, value>对。用于Map-Reduce框架,可以被Mapper或者Reducer使用  
*/
public interface OutputCollector<K extends WritableComparable,
                                 V extends Writable> {

/** Adds a key/value pair to the output.
   *
   * @param key the key to collect.
   * @param value to value to collect.
   * @throws IOException
   */
void collect(K key, V value) throws IOException;
}

 

收集<key,value>对很好理解的,比如,读取文本文件中的一行,使用StringTokenizer提取后,假如得到一个单词 “shirdrn”,要对其统计词频,并且是第一次出现这个单词,则<key,value>对<shridrn,1>就可以表 示,然后继续读取;如果再次碰到“shirdrn”这个单词,依然如上面,<key,value>对就 是<shridrn,1>。在执行Map任务的时候,<key,value>对可能存在重复,因为Map任务没有对它进行合并。

如果必要的话。可以使用Combiner在Map完成之后先进行一次中间结果的合并,对上面出现两次的“shirdrn”,合并后就 是<shridrn,2>。注意了,这里只是一个Map任务,假如另一个Map任务也多次出现“shirdrn”这个单词,例如执行一个 Combiner合并后变为<shridrn,4>。

使用Combiner合并后得到的结果仍然是一个中间结果,也就是说,对于某项任务(对应着多个Map子任务)执行完Map任务后,例如上面的两 个,全部的中间结果中存在这样两个键值对:<shridrn,2>和<shridrn,4>,而我们的目的是要统计 “shirdrn”的词频,期望得到的结果是<shridrn,6>,这就要执行Reduce任务了,Reduce任务输出的不是中间结果 了,是最终结果,即有一个输出或者0个输出。

另外,执行Combiner进行中间结果的合并输出中间结果之前,可能需要进行一个排序操作,对Map任务执行的输出结果进行排序后在进行Combiner合并。

OutputCollector<Text, IntWritable>是一个接口,要想知道它的collect方法是如何进行收集数据的,需要看它的具体实现类了,先看一看它的两个具体实现类:



 

首先,在MapTask类中定义了一个MapOutputCollector<K extends WritableComparable, V extends Writable>接口,它继承自OutputCollector<Text, IntWritable>接口,从而DirectMapOutputCollector<K extends WritableComparable,V extends Writable>类实现了MapOutputCollector<K, V>接口,MapOutputBuffer类也实现了MapOutputCollector接口。

其中,DirectMapOutputCollector类和MapOutputBuffer类都是OutputCollector接口的间接实 现,但是在这两个具体实现类中,定义的collect()方法的功能是不同的,DirectMapOutputCollector类中的 collect()方法实现非常简单而且直接:

Java代码   收藏代码
  1.   public   void  collect(K key, V value)  throws  IOException {  
  2.   this .out.write(key, value);  
  3. }  
     public void collect(K key, V value) throws IOException {
      this.out.write(key, value);
    }

 

这里主要是使用了RecordWriter接口的具体实现类LineRecordWriter的write()方法来完成单词(应该是Map任务执 行生成的key)的收集,LineRecordWriter类是定义在 org.apache.hadoop.mapred.TextOutputFormat类内容的一个静态类,看看write()方法是如何收集写入的:

Java代码   收藏代码
  1. public   synchronized   void  write(K key, V value)  
  2.  throws  IOException {  
  3.   
  4.  boolean  nullKey = key ==  null  || key  instanceof  NullWritable;  
  5.  boolean  nullValue = value ==  null  || value  instanceof  NullWritable;  
  6.  if  (nullKey && nullValue) {  
  7.    return ;  
  8.  }  
  9.  if  (!nullKey) {  
  10.    writeObject(key);  
  11.  }  
  12.  if  (!(nullKey || nullValue)) {  
  13.    out.write(tab);  
  14.  }  
  15.  if  (!nullValue) {  
  16.    writeObject(value);  
  17.  }  
  18.  out.write(newline);  
     public synchronized void write(K key, V value)
      throws IOException {

      boolean nullKey = key == null || key instanceof NullWritable;
      boolean nullValue = value == null || value instanceof NullWritable;
      if (nullKey && nullValue) {
        return;
      }
      if (!nullKey) {
        writeObject(key);
      }
      if (!(nullKey || nullValue)) {
        out.write(tab);
      }
      if (!nullValue) {
        writeObject(value);
      }
      out.write(newline);
    }

 

其实非常容易,当key和value都不为空的时候才将它们收集写入,并且适当处理TAB和换行,上面的tab和newline在类中静态定义:

Java代码   收藏代码
  1. static  {  
  2.  try  {  
  3.    tab = "/t" .getBytes(utf8);  
  4.    newline = "/n" .getBytes(utf8);  
  5.  } catch  (UnsupportedEncodingException uee) {  
  6.    throw   new  IllegalArgumentException( "can't find "  + utf8 +  " encoding" );  
  7.  }  
     static {
      try {
        tab = "/t".getBytes(utf8);
        newline = "/n".getBytes(utf8);
      } catch (UnsupportedEncodingException uee) {
        throw new IllegalArgumentException("can't find " + utf8 + " encoding");
      }
    }

 

现在明白多了,如果在看一看writeObject()方法的实现就更加明白了:

Java代码   收藏代码
  1. private   void  writeObject(Object o)  throws  IOException {  
  2.  if  (o  instanceof  Text) {  
  3.    Text to = (Text) o;  
  4.    out.write(to.getBytes(), 0 , to.getLength());  
  5.  } else  {  
  6.    out.write(o.toString().getBytes(utf8));  
  7.  }  
     private void writeObject(Object o) throws IOException {
      if (o instanceof Text) {
        Text to = (Text) o;
        out.write(to.getBytes(), 0, to.getLength());
      } else {
        out.write(o.toString().getBytes(utf8));
      }
    }

 

其实将一个<key,value>对设置到了一个Text的对象中。Text对象当然可以包含很多 个<key,value>对了,只要使用指定的分隔符分割就行了。上面已经使用回车换行符了,具体样式是这样的:设置一个key,例如 “shirdrn”,再设置一个value,例如6,然后在来一个回车换行符,多个的话,就形如下所示:

Java代码   收藏代码
  1. shirdrn       6   
  2. master       2    
  3. hear       4   
shirdrn      6
master       2 
hear       4

 

在writeObject()方法中,还要进行输出呢,使用java.io.DataOutputStream.write()方法进行输出的,写 到指定的文件系统或者缓存中,这里是中间结果应该是写入到缓存中,因为Map任务结束会立即执行Reduce任务来对中间结果进行合并输出。

  • 大小: 15.6 KB

再看一下Reduce的实现:

Java代码   收藏代码
  1. /**  
  2.    * Reduce是一个内部静态类。作为统计单词数量的中间结果类,由于这个例子简单无须执行中间结果的合并。  
  3.    */   
  4. public   static   class  Reduce  extends  MapReduceBase  
  5.     implements  Reducer<Text, IntWritable, Text, IntWritable> {  
  6.       
  7.     public   void  reduce(Text key, Iterator<IntWritable> values,  
  8.                        OutputCollector<Text, IntWritable> output,   
  9.                        Reporter reporter) throws  IOException {  
  10.       int  sum =  0 ;  
  11.       while  (values.hasNext()) {  
  12.         sum += values.next().get();  
  13.       }  
  14.       output.collect(key, new  IntWritable(sum));  
  15.     }  
  16. }  
/**
   * Reduce是一个内部静态类。作为统计单词数量的中间结果类,由于这个例子简单无须执行中间结果的合并。
   */
public static class Reduce extends MapReduceBase
    implements Reducer<Text, IntWritable, Text, IntWritable> {
    
    public void reduce(Text key, Iterator<IntWritable> values,
                       OutputCollector<Text, IntWritable> output, 
                       Reporter reporter) throws IOException {
      int sum = 0;
      while (values.hasNext()) {
        sum += values.next().get();
      }
      output.collect(key, new IntWritable(sum));
    }
}

 

reduce方法的第二个参数为Iterator<IntWritable> values,是一个迭代器类型,即是多个value的迭代器,通过这个迭代器可以得到多个value。又由于第一个参数指定了key,那么这个迭代器就 是与这个key相关的了,即每个value都是key的value,如果统计词频,只要将多个value进行求和运算即可。

最后同样要输出了,这次是输出到文件系统中的指定文件中了,因为是最终结果。

最后一个就是Map/Reduce的核心驱动部分了,目的就是要让WordCount这个工具正常地运行起来,看run方法的实现:

Java代码   收藏代码
  1. /**  
  2.    * map/reduce程序的驱动部分,用于实现提交map/reduce任务。  
  3.    */   
  4. public   int  run(String[] args)  throws  Exception {  
  5.     JobConf conf = new  JobConf(getConf(), WordCount. class );  
  6.     conf.setJobName("wordcount" );  
  7.   
  8.     // the keys are words (strings)   
  9.     conf.setOutputKeyClass(Text.class );  
  10.     // the values are counts (ints)   
  11.     conf.setOutputValueClass(IntWritable.class );  
  12.       
  13.     conf.setMapperClass(MapClass.class );          
  14.     conf.setCombinerClass(Reduce.class );  
  15.     conf.setReducerClass(Reduce.class );  
  16.       
  17.     List<String> other_args = new  ArrayList<String>();  
  18.     for ( int  i= 0 ; i < args.length; ++i) {  
  19.       try  {  
  20.         if  ( "-m" .equals(args[i])) {  
  21.           conf.setNumMapTasks(Integer.parseInt(args[++i]));  
  22.         } else   if  ( "-r" .equals(args[i])) {  
  23.           conf.setNumReduceTasks(Integer.parseInt(args[++i]));  
  24.         } else  {  
  25.           other_args.add(args[i]);  
  26.         }  
  27.       } catch  (NumberFormatException except) {  
  28.         System.out.println("ERROR: Integer expected instead of "  + args[i]);  
  29.         return  printUsage();  
  30.       } catch  (ArrayIndexOutOfBoundsException except) {  
  31.         System.out.println("ERROR: Required parameter missing from "  +  
  32.                            args[i-1 ]);  
  33.         return  printUsage();  
  34.       }  
  35.     }  
  36.     // Make sure there are exactly 2 parameters left.   
  37.     if  (other_args.size() !=  2 ) {  
  38.       System.out.println("ERROR: Wrong number of parameters: "  +  
  39.                          other_args.size() + " instead of 2." );  
  40.       return  printUsage();  
  41.     }  
  42.     conf.setInputPath(new  Path(other_args.get( 0 )));  
  43.     conf.setOutputPath(new  Path(other_args.get( 1 )));  
  44.           
  45.     JobClient.runJob(conf);  
  46.     return   0 ;  
  47. }  
/**
   * map/reduce程序的驱动部分,用于实现提交map/reduce任务。
   */
public int run(String[] args) throws Exception {
    JobConf conf = new JobConf(getConf(), WordCount.class);
    conf.setJobName("wordcount");

    // the keys are words (strings)
    conf.setOutputKeyClass(Text.class);
    // the values are counts (ints)
    conf.setOutputValueClass(IntWritable.class);
    
    conf.setMapperClass(MapClass.class);        
    conf.setCombinerClass(Reduce.class);
    conf.setReducerClass(Reduce.class);
    
    List<String> other_args = new ArrayList<String>();
    for(int i=0; i < args.length; ++i) {
      try {
        if ("-m".equals(args[i])) {
          conf.setNumMapTasks(Integer.parseInt(args[++i]));
        } else if ("-r".equals(args[i])) {
          conf.setNumReduceTasks(Integer.parseInt(args[++i]));
        } else {
          other_args.add(args[i]);
        }
      } catch (NumberFormatException except) {
        System.out.println("ERROR: Integer expected instead of " + args[i]);
        return printUsage();
      } catch (ArrayIndexOutOfBoundsException except) {
        System.out.println("ERROR: Required parameter missing from " +
                           args[i-1]);
        return printUsage();
      }
    }
    // Make sure there are exactly 2 parameters left.
    if (other_args.size() != 2) {
      System.out.println("ERROR: Wrong number of parameters: " +
                         other_args.size() + " instead of 2.");
      return printUsage();
    }
    conf.setInputPath(new Path(other_args.get(0)));
    conf.setOutputPath(new Path(other_args.get(1)));
        
    JobClient.runJob(conf);
    return 0;
}

 

在run()方法中,值得注意的是JobConf这个类,它是一个任务配置类。它是Configuration的子类,因为在继承了Configuration的关于Hadoop的基本配置以外,还有自己的一些针对任务的相关配置。

JobConf类应该是相当重要的。我们主要围绕在WordCount这个工具中使用到的一些方法进行了解。

首先要实例化一个JobConf类的对象:

Java代码   收藏代码
  1. JobConf conf =  new  JobConf(getConf(), WordCount. class );  
JobConf conf = new JobConf(getConf(), WordCount.class);

 

通过这个初始化代码行来看一下JobConf类的构造方法:

Java代码   收藏代码
  1. public  JobConf(Configuration conf, Class exampleClass) {  
  2.     this (conf);    
  3.     setJarByClass(exampleClass);  
  4. }  
public JobConf(Configuration conf, Class exampleClass) {
    this(conf);  
    setJarByClass(exampleClass);
}

 

首先, 调用该类的具有一个Configuration类型参数的构造方法,其实就是继承自Configuration类,如下所示:

Java代码   收藏代码
  1. public  JobConf(Configuration conf) {  
  2.     super (conf);  
  3. }  
public JobConf(Configuration conf) {
    super(conf);
}

 

然后,调用setJarByClass()方法,根据指定的类名称来设置当前运行任务的任务配置包含的Jar文件,方法如下所示:

Java代码   收藏代码
  1. public   void  setJarByClass(Class cls) {  
  2.     String jar = findContainingJar(cls);  
  3.     if  (jar !=  null ) {  
  4.       setJar(jar);  
  5.     }     
  6. }  
public void setJarByClass(Class cls) {
    String jar = findContainingJar(cls);
    if (jar != null) {
      setJar(jar);
    }   
}

 

这里首先要查找包含的Jar文件(返回的是Jar文件的字符串描述),如果不空再调用 setJar(jar);为任务配置进行设置。

看一下如何进行查找的,在findContainingJar()方法中有实现过程:

Java代码   收藏代码
  1. private   static  String findContainingJar(Class my_class) {  
  2.     ClassLoader loader = my_class.getClassLoader();   // 获取到指定类Class my_class的类加载器   
  3.     String class_file = my_class.getName().replaceAll("//.""/" ) +  ".class"// 获取到类文件   
  4.     try  {  
  5.       for (Enumeration itr = loader.getResources(class_file);  
  6.           itr.hasMoreElements();) {  
  7.         URL url = (URL) itr.nextElement();  
  8.         if  ( "jar" .equals(url.getProtocol())) {  // 迭代出的URL是否支持jar协议   
  9.           String toReturn = url.getPath(); // 获取这个URL的path   
  10.           if  (toReturn.startsWith( "file:" )) {   
  11.             toReturn = toReturn.substring("file:" .length());  //   提取path中“file:”字符串后面的文件名字符串   
  12.           }  
  13.           toReturn = URLDecoder.decode(toReturn, "UTF-8" );  // 解码   
  14.           return  toReturn.replaceAll( "!.*$""" );  // 格式化:去掉文件名称中的"!.*$"   
  15.         }  
  16.       }  
  17.     } catch  (IOException e) {  
  18.       throw   new  RuntimeException(e);  
  19.     }  
  20.     return   null ;  
  21. }  
private static String findContainingJar(Class my_class) {
    ClassLoader loader = my_class.getClassLoader();   // 获取到指定类Class my_class的类加载器
    String class_file = my_class.getName().replaceAll("//.", "/") + ".class"; // 获取到类文件
    try {
      for(Enumeration itr = loader.getResources(class_file);
          itr.hasMoreElements();) {
        URL url = (URL) itr.nextElement();
        if ("jar".equals(url.getProtocol())) { // 迭代出的URL是否支持jar协议
          String toReturn = url.getPath(); // 获取这个URL的path
          if (toReturn.startsWith("file:")) { 
            toReturn = toReturn.substring("file:".length()); //   提取path中“file:”字符串后面的文件名字符串
          }
          toReturn = URLDecoder.decode(toReturn, "UTF-8"); // 解码
          return toReturn.replaceAll("!.*$", ""); // 格式化:去掉文件名称中的"!.*$"
        }
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    return null;
}

 

查找获得了一个jar字符串,然后setJar(jar) ,方法如下:

Java代码   收藏代码
  1. /**  
  2.    * Set the user jar for the map-reduce job.  
  3.    *   
  4.    * @param jar the user jar for the map-reduce job.  
  5.    */   
  6. public   void  setJar(String jar) { set( "mapred.jar" , jar); }  
/**
   * Set the user jar for the map-reduce job.
   * 
   * @param jar the user jar for the map-reduce job.
   */
public void setJar(String jar) { set("mapred.jar", jar); }

 

上面set("mapred.jar", jar);方法是继承自Configuration类的方法,如下所示:

Java代码   收藏代码
  1. /**   
  2.    * Set the <code>value</code> of the <code>name</code> property.  
  3.    *   
  4.    * @param name property name.  
  5.    * @param value property value.  
  6.    */   
  7. public   void  set(String name, String value) {  
  8.     getOverlay().setProperty(name, value);  
  9.     getProps().setProperty(name, value);  
  10. }  
/** 
   * Set the <code>value</code> of the <code>name</code> property.
   * 
   * @param name property name.
   * @param value property value.
   */
public void set(String name, String value) {
    getOverlay().setProperty(name, value);
    getProps().setProperty(name, value);
}

 

上面把set("mapred.jar", jar); 设置到Properties变量中了,而properties和overlay都是Configuration类的成员:

Java代码   收藏代码
  1. private  Properties properties;  
  2. private  Properties overlay;  
private Properties properties;
private Properties overlay;

 

到这里,JobConf conf已经进行了基本的任务配置,加载类设置。

接着就要继续更详细的配置了。

设置任务名称:

Java代码   收藏代码
  1. conf.setJobName( "wordcount" );  
conf.setJobName("wordcount");

 

setJobName()方法实现:

Java代码   收藏代码
  1. public   void  setJobName(String name) {  
  2.     set("mapred.job.name" , name);  
  3. }  
public void setJobName(String name) {
    set("mapred.job.name", name);
}

 

即,任务名称为wordcount。

设置任务的key的Class:

Java代码   收藏代码
  1. // the keys are words (strings)   
  2. conf.setOutputKeyClass(Text.class );  
    // the keys are words (strings)
    conf.setOutputKeyClass(Text.class);

 

对应于:

Java代码   收藏代码
  1.    public   void  setOutputKeyClass(Class<?  extends  WritableComparable> theClass) {  
  2.     setClass("mapred.output.key.class" , theClass, WritableComparable. class );  
  3. }  
  public void setOutputKeyClass(Class<? extends WritableComparable> theClass) {
    setClass("mapred.output.key.class", theClass, WritableComparable.class);
}

 

设置任务的value的Class:

Java代码   收藏代码
  1. // the values are counts (ints)   
  2. conf.setOutputValueClass(IntWritable.class );  
    // the values are counts (ints)
    conf.setOutputValueClass(IntWritable.class);

 

对应于:

Java代码   收藏代码
  1. public   void  setOutputValueClass(Class<?  extends  Writable> theClass) {  
  2.     setClass("mapred.output.value.class" , theClass, Writable. class );  
  3. }  
public void setOutputValueClass(Class<? extends Writable> theClass) {
    setClass("mapred.output.value.class", theClass, Writable.class);
}

 

设置MapperClass、CombinerClass、ReducerClass的Class:

Java代码   收藏代码
  1. conf.setMapperClass(MapClass. class );          
  2. conf.setCombinerClass(Reduce.class );  
  3. conf.setReducerClass(Reduce.class );  
    conf.setMapperClass(MapClass.class);        
    conf.setCombinerClass(Reduce.class);
    conf.setReducerClass(Reduce.class);

 

其中,CombinerClass就是使用的ReducerClass,就在ReducerClass中完成一次完成合并简化操作。

接着往下看,List<String> other_args是接收输入的一些特定参数,这里是指设置Map任务和Reduce任务的数量,即-m和-r参数,通过一个循环判断是否指定了这些参 数,如果指定了,要分别将其设置到任务的配置中去,以便任务启动之时能够按照我们定制的方式进行执行。

Java代码   收藏代码
  1. if  ( "-m" .equals(args[i])) {  
  2.   conf.setNumMapTasks(Integer.parseInt(args[++i]));  
  3. else   if  ( "-r" .equals(args[i])) {  
  4.   conf.setNumReduceTasks(Integer.parseInt(args[++i]));  
  5. else  {  
  6.   other_args.add(args[i]);  
  7. }  
        if ("-m".equals(args[i])) {
          conf.setNumMapTasks(Integer.parseInt(args[++i]));
        } else if ("-r".equals(args[i])) {
          conf.setNumReduceTasks(Integer.parseInt(args[++i]));
        } else {
          other_args.add(args[i]);
        }

 

一个是设置Map任务数量:

Java代码   收藏代码
  1. public   void  setNumMapTasks( int  n) { setInt( "mapred.map.tasks" , n); }  
public void setNumMapTasks(int n) { setInt("mapred.map.tasks", n); }

 

另一个是设置Reduce任务数量:

Java代码   收藏代码
  1. public   void  setNumReduceTasks( int  n) { setInt( "mapred.reduce.tasks" , n); }  
public void setNumReduceTasks(int n) { setInt("mapred.reduce.tasks", n); }

 

使用命令行,要指定任务输入目录和输出目录:

Java代码   收藏代码
  1. conf.setInputPath( new  Path(other_args.get( 0 )));  
  2. conf.setOutputPath(new  Path(other_args.get( 1 )));  
    conf.setInputPath(new Path(other_args.get(0)));
    conf.setOutputPath(new Path(other_args.get(1)));

 

设置输入目录的方法调用:

Java代码   收藏代码
  1. public   void  setInputPath(Path dir) {  
  2.     dir = new  Path(getWorkingDirectory(), dir);  
  3.     set("mapred.input.dir" , dir.toString());  
  4. }  
public void setInputPath(Path dir) {
    dir = new Path(getWorkingDirectory(), dir);
    set("mapred.input.dir", dir.toString());
}

 

设置任务输出目录的方法调用:

Java代码   收藏代码
  1. public   void  setOutputPath(Path dir) {  
  2.  dir = new  Path(getWorkingDirectory(), dir);  
  3.  set("mapred.output.dir" , dir.toString());  
   public void setOutputPath(Path dir) {
    dir = new Path(getWorkingDirectory(), dir);
    set("mapred.output.dir", dir.toString());
}

 

它们都调用了一个得到当前工作目录的绝对路径的方法getWorkingDirectory(),如下所示:

Java代码   收藏代码
  1. /**  
  2.    * Get the current working directory for the default file system.  
  3.    *   
  4.    * @return the directory name.  
  5.    */   
  6. public  Path getWorkingDirectory() {  
  7.     String name = get("mapred.working.dir" );  
  8.     if  (name !=  null ) {  
  9.       return   new  Path(name);  
  10.     } else  {  
  11.       try  {  
  12.         Path dir = FileSystem.get(this ).getWorkingDirectory();  
  13.         set("mapred.working.dir" , dir.toString());  
  14.         return  dir;  
  15.       } catch  (IOException e) {  
  16.         throw   new  RuntimeException(e);  
  17.       }  
  18.     }  
  19. }  
/**
   * Get the current working directory for the default file system.
   * 
   * @return the directory name.
   */
public Path getWorkingDirectory() {
    String name = get("mapred.working.dir");
    if (name != null) {
      return new Path(name);
    } else {
      try {
        Path dir = FileSystem.get(this).getWorkingDirectory();
        set("mapred.working.dir", dir.toString());
        return dir;
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }
}

 

最后,任务配置完成后,进行启动:

Java代码   收藏代码
  1. JobClient.runJob(conf);  
JobClient.runJob(conf);

 

这个启动过程可是非常复杂了,你可以通过JobClient类的runJob()方法看到。

 

转自:http://radarradar.iteye.com/blog/289261

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值