wuyijia

导航

八斗30_day02_作业

作业1:理解前10个案例代码逻辑

01_WordCount:

  1. Hadoop MapReduce 程序中的Driver部分,即将所有组件(如 Mapper、Reducer、Combiner等)整合起来的主要逻辑部分。

  2.MapReduce 程序的 Mapper 会将输入数据中的每一行转换成一个键值对 (Text, LongWritable)。

  3.这些键值对会被传递到 Reduce 阶段进行进一步处理。在Reduce阶段中,它会以单词为键,对应的值列表为值,合并相同单词出现次数,并输出对应的 (Text, LongWritable) 类型的键值对。

 

Context,表示 MapReduce 程序执行期间的上下文信息。它通过 context.write() 方法将 Mapper 的输出数据传递给 Reducer 阶段。具体来说,Context 包含了以下常用方法:

  1.context.write(K key, V value):将 Mapper 的输出键值对以 (K, V) 的形式写入上下文中。

  2.context.getConfiguration():获取 Hadoop 配置信息,如文件系统类型、副本数、存储路径等。

  3.context.getInputSplit():获取当前输入数据所在的分片信息。

  4.context.getCounter(Enum<?> counterName):用来获取计数器信息,可以用来监控程序的执行情况。

  5.context.getCacheFiles():获取程序缓存的文件。

【代码】

WordMapper.java

 1 //mapper进程,每一个split(block)会启动该类,
 2 public class wordMapper extends Mapper<LongWritable,Text,Text,LongWritable>{
 3 //   Mapper<LongWritable,Text,Text,LongWritable>
 4 //    map_in_key ,map_in_value: LongWritable,Text(String)  偏移量,Harry hung back for a last word with Ron and Hermione.
 5 //    map_out_key ,map_out_value:Text,LongWritable  :  Harry,1     hung,1
 6     @Override
 7 //    map,是一条一条执行,只针对一条数据
 8     protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
 9 //  LongWritable key: 指的是偏移量。类型为 LongWritable,表示当前输入记录的偏移量,它对应于输入文件中每一行数据的行号或字节偏移量。
10 // Text value: 每一行的内容,类型为 Text,表示输入数据中的一行文本,它包含了行内所有的信息和字符。需要将这个 Text 类型的 value 转换为字符串进行后续处理。
11 // Context context:上下文 负责管理的读取hdfs 数据信息,计算上下文;类型为 Context,表示 MapReduce 程序执行期间的上下文信息。它通过 context.write() 方法将 Mapper 的输出数据传递给 Reducer 阶段。
12 
13 //        value  = Harry hung back for a last word with Ron and Hermione.
14 //        1.每行读取文字,变成java的string
15         String line =  value.toString();
16 
17 //        2.业务切分每个单词,切分为字符串数组
18 //        [Harry,hung,back,for,a,last,word,with,Ron,and,Hermione]
19         String[] data = line.split(" ");
20 
21 //        3.遍历字符串数组,然后一步一步输出(word,1)
22         for (String word:
23                 data
24                 ) {
25             //word,1 ----->key = word,value = 1
26             context.write(new Text(word),new LongWritable(1));
27         }
28     }

WordReducer.java

 1 public class  wordReducer  extends Reducer<Text,LongWritable,Text,LongWritable>{
 2 ////    block1      word1:3,word2:91
 3 ////     block2      word1:153,word2:191
 4 //
 5     @Override
 6     protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
 7 //  今天,[1,1,1,1,1,1,1,1] values 为 Iterable 集合类型,它包含了该单词在 Mapper 输出中的所有出现次数。
 8         long count = 0;
 9 
10         for (LongWritable v: values) {
11                 count+=v.get();
12         }
13 
14        // 八斗,15 
15         context.write(new Text(key),new LongWritable(count));
16         /*
17         Reducer 阶段的输出(即 MapReduce 作业的最终结果)会被写入 HDFS 或其他存储系统中,
18         可以使用 Hadoop 提供的 FileSystem API 来从外部程序中访问和处理这些结果。
19          */
20     }
21 }

WordCountDriver.java

 1 public class WordCountDriver {
 2     public static void main(String[] args) throws Exception {
 3 //        hadoop jar study_demo.jar hadoop_test.word_count_demo_01.WordCountDriver
 4         System.setProperty("HADOOP_USER_NAME","root");
 5         Configuration conf = new Configuration();
 6         Job job = Job.getInstance(conf);
 7 
 8 
 9         //程序入口,driver类 ,设置mr作业的jar包,以及对应的主类
10         job.setJarByClass(WordCountDriver.class);
11         //设置mapper的类(蓝图,人类,鸟类),实例(李冰冰,鹦鹉2号),对象
12         //指定了 Mapper 类和 Reducer 类的类别和实例。
13         job.setMapperClass(wordMapper.class);
14         //设置mapper的输出键和值的类型
15         job.setMapOutputKeyClass(Text.class);
16         job.setMapOutputValueClass(LongWritable.class);
17 
18         job.setReducerClass(wordReducer.class);
19         job.setOutputKeyClass(Text.class);
20         job.setOutputValueClass(LongWritable.class);
21 
22 //       需要指定combine的实现类,不用指定输出key和value类型
23 //        job.setCombinerClass(WordCountCombine.class);
24 //        输入文件
25         FileInputFormat.setInputPaths(job, new Path("/badou30/day01/acticle.txt"));
26         /*
27         在函数内部,首先调用 FileSystem.get(conf) 方法获取一个指向 Hadoop 分布式文件系统的 FileSystem 实例,
28         然后使用 fs.exists(new Path(path)) 方法检查给定路径下的文件或目录是否存在,并返回其布尔值结果。
29          */
30         if( Utils_hadoop.testExist(conf,"/badou30/day01/word_count/word_count_result")){
31             Utils_hadoop.rmDir(conf,"/badou30/day01/word_count/word_count_result");}
32         FileOutputFormat.setOutputPath(job, new Path("/badou30/day01/word_count/word_count_result"));
33         job.waitForCompletion(true);
34         /*
35         这行代码的作用是提交 MapReduce 作业并等待其完成。
36         具体来说,它将作业提交到 Hadoop 集群进行执行,并阻塞当前线程,直到作业完成或发生错误。
37         如果作业成功执行完毕,则返回 true,否则返回 false。
38 
39         注意,job.waitForCompletion(true) 方法会将 Hadoop 集群上下文中的所有信息传输到客户端并进行汇总,
40         因此它可能会消耗相当大的网络带宽和计算资源。
41         为了避免这种情况,我们可以将其设置为 false,然后使用 Hadoop 的 Counter 对象来获取作业执行过程中的计数器信息。
42         这样可以显著减少网络传输量和资源消耗。
43          */
44 
45     }
46 }

 WordCountCombine.java

 1 public class WordCountCombine extends Reducer<Text,LongWritable,Text,LongWritable>{
 2 //Reducer<key_in,value_in,key_out,value_out>
 3 //  key_in,value_in 为map端的输出
 4 //    key_out,value_out 为reduce端的输入
 5 //    这个就是做了局部(本机器下的map汇总)的汇总
 6     @Override
 7     protected void reduce(Text key, Iterable<LongWritable> values,
 8             Context context) throws IOException, InterruptedException {
 9         long result=0;
10         for(LongWritable value:values){
11             result=result+value.get();
12         }
13 //    block1      word1:3,word2:91
14 //     block2      word1:153,word2:191
15         context.write(key, new LongWritable(result));
16     }
17 }

02_对ip数据进行去重 : map阶段将IP值映射成key,reduce阶段将同一个key的值进行聚合。即达到对ip数据去重。

03_分组求平均值 : mapper阶段:将每一行的数据中的班级设置为key,分数作为value;reducer阶段:按key求和求平均。

04_求最大最小值 : map:key=year,value=temp,reduce:根据key找每一年的最大温度和最小温度。

05_序列化机制:集群工作过程中,需要用到RPC通信,所以MR处理的对象必须可以进行序列化/反序列操作。Hadoop利用的是avro实现的序列化和反序列,并且在其基础上提供了便捷的API要序列化的对象必要实现相关的接口。

  1.序列化:数据做压缩,方便传输,json,proto,picke。 map到reduce的传输过程:就是序列化传输。

  2.反序列化:读取序列化数据的过程。

  3.在 MapReduce 程序中,如果需要在Map和Reduce阶段之间传递复杂类型的数据,如自定义类或数组等,就需要将它们序列化并转换成可读写的字节码数据。在Hadoop中,我们可以通过实现Writable接口,并重写write()和readFields()方法来实现序列化和反序列化过程。

实现序列化和反序列化代码:FlowBean.java

 1 public class FlowBean  implements Writable{
 2 
 3     private String phone;
 4     private String add;
 5     private String name;
 6     private long consum;
 7 
 8 //    序列化
 9     @Override
10     public void write(DataOutput out) throws IOException {
11 
12         out.writeUTF(phone);
13         out.writeUTF(add);
14         out.writeUTF(name);
15         out.writeLong(consum);
16     }
17     /*
18     在序列化过程中,首先调用 write() 方法,并使用 DataOutput 输出流对上述四个属性进行写操作。
19     注意,Hadoop的DataOutput流是类似于 Java 中的 DataOutputStream 的二进制输出流,
20     它可以将 Java 基本类型与 String 类型的数据转换为可读的二进制码。
21     在本例中,out.writeUTF()方法用于将字符串类型的 phone、add、name 写入输出流中,
22     out.writeLong()方法用于将长整型数据consum写入输出流中。
23      */
24 
25 //    反序列化,跟序列化的顺序不能改变
26     @Override
27     public void readFields(DataInput in) throws IOException {
28         this.phone=in.readUTF();
29         this.add=in.readUTF();
30         this.name=in.readUTF();
31         this.consum=in.readLong();
32     }
33     /*
34     在反序列化过程中,调用 readFields() 方法,并使用 DataInput 输入流对上述四个属性进行读取操作,以还原出原始的 Bean 对象。
35     注意,Hadoop 的 DataInput 流是类似于 Java 中的 DataInputStream 的二进制输入流,它可以从字节码数据中读取 Java 基本类型和 String 类型的数据。
36      */
37 
38     public String getPhone() {
39         return phone;
40     }
41 
42     public String getAdd() {
43         return add;
44     }
45 
46     public String getName() {
47         return name;
48     }
49 
50     public long getConsum() {
51         return consum;
52     }
53 
54     public void setPhone(String phone) {
55         this.phone = phone;
56     }
57 
58     public void setAdd(String add) {
59         this.add = add;
60     }
61 
62     public void setName(String name) {
63         this.name = name;
64     }
65 
66     public void setConsum(long consum) {
67         this.consum = consum;
68     }
69 
70     @Override
71     public String toString() {
72         return "FlowBean [phobe="+phone+",add="+add+",name="+name+",consum="+consum+"]";
73     }
74 }

FlowMapper.java

 1 public class FlowMapper extends Mapper<LongWritable, Text, Text, FlowBean> {
 2     @Override
 3     protected void map(LongWritable key, Text value, Context context)
 4             throws IOException, InterruptedException {
 5 //        13877779999 bj zs 2145
 6 
 7         String line=value.toString();
 8         //#实例化
 9         FlowBean flowBean=new FlowBean();
10 
11         //给一个实例的属性赋予初始值,
12 //        13877779999
13         flowBean.setPhone(line.split(" ")[0]);
14 //         bj
15         flowBean.setAdd(line.split(" ")[1]);
16 //        zs
17         flowBean.setName(line.split(" ")[2]);
18         flowBean.setConsum(Integer.parseInt(line.split(" ")[3]));
19         System.out.println(flowBean);
20 //        zs
21         context.write(new Text(flowBean.getName()), flowBean);
22     }
23 }

FlowReducer.java

 1 public class FlowReducer extends Reducer<Text, FlowBean,Text,FlowBean> {
 2 
 3     @Override
 4     protected void reduce(Text name, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
 5        //重新实例化了一个类,他目的是计算用户消费总额
 6         FlowBean tmp=new FlowBean();
 7 // 李四 [flowbean[phone=13766668888,add=sh,name=ls,consum=1000],flowbean[phobe=13766668888,add=BJ,name=ls,consum=21000]]
 8         for(FlowBean flowbean:values){
 9 //        flowbean    FlowBean [phobe=13766668888,add=sh,name=ls,consum=9844]
10 // tmp.add=flowbean.getAdd()
11 //            System.out.println("FLOABEAN"+flowbean);
12             tmp.setAdd(flowbean.getAdd());
13             tmp.setPhone(flowbean.getPhone());
14             tmp.setName(flowbean.getName());
15 //     tmp.getComsum(初始化是0)+flowbean.getConsum()[9844]
16 //            在第一轮tmp.getConsum()=[9844]
17 
18 //            第二轮FlowBean [phobe=13766668888,add=sh,name=ls,consum=1000]
19 //        tmp.Consum    =tmp.getComsum(9844)+flowbean.getConsum()[100]
20             System.out.println(name+"金额"+tmp.getConsum());
21             tmp.setConsum(tmp.getConsum()+flowbean.getConsum());
22 
23         }
24         context.write(name, tmp);
25     }
26 }

 06_分区:

  1.FlowBean.java:实现一个writable接口,并重写两个方法,write表示序列化,readFields表示反序列化

  2.FlowMapper.java

  3.FlowPartitioner.java:对addr进行分区:

 1 //Text,FlowBean  keymapput  keyvalueout
 2 public class FlowPartitioner extends Partitioner<Text,FlowBean> {
 3     @Override
 4     //<Text,FlowBean>指的是map的key,value
 5     public int getPartition(Text key, FlowBean value, int numPartitions) {
 6 //数据倾斜解决
 7 //        Random R = new Random();
 8 //
 9 //        String hash_key=key.toString()+String.valueOf(R.nextInt());
10 //        return (hash_key .hashCode() & Integer.MAX_VALUE) % numPartitions;
11 //        if(value.getName().equals("wyf")){
12 //            Random R = new Random();
13 ////
14 //            String hash_key = key.toString()+String.valueOf(R.nextInt());
15 //        }
16 
17         if(value.getAddr().equals("sh")){
18             return 0;
19         }
20         if(value.getAddr().equals("bj")){
21             return 1;
22         }
23         else{
24             return 2;
25         }
26     }
27 
28 }

  4.FlowReducer.java

 1 public class FlowReducer extends Reducer<Text, FlowBean,Text,FlowBean> {
 2 
 3     @Override
 4     protected void reduce(Text name, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
 5         FlowBean tmp=new FlowBean();
 6         for(FlowBean flowbean:values){
 7             tmp.setAddr(flowbean.getAddr());
 8             tmp.setPhone(flowbean.getPhone());
 9             tmp.setName(flowbean.getName());
10             tmp.setFlow(tmp.getFlow()+flowbean.getFlow()); //tmp.getFlow()初始值为0
11         }
12         context.write(name, tmp);
13     }
14 }

  5.FlowDriver.java中增加:

1 //配置partition,只多不少
2 //job.setNumReduceTasks(4)指定了该任务的Reduce任务数为4个
3 job.setNumReduceTasks(4);
4 job.setPartitionerClass(FlowPartitioner.class);
5 //FlowPartitioner是一个自定义的Partitioner类,用于将Map阶段输出的key-value对进行分区操作,
6 //将相同key的数据发送到同一个Reduce任务中进行处理。

07_combiner:

  合并的目的是减少Reduce端迭代的次数combiner是实现Mapper端进行key的归并,combiner具有类似本地的reduce功能。如果不用combiner,那么所有的结果都是reduce完成,效率会很低。使用combiner先做合并,然后发往reduce。

08_排序和全排序:

  排序:

  1.WritableComparable是Hadoop中的一个接口,用于定义可序列化和可比较的数据类型,以便在MapReduce任务中进行序列化和排序。

  2.Movie.java:

/*
Movie类还需要实现compareTo方法,实现在Reduce阶段对对象进行比较和排序的方式。
在该方法中,返回一个int型的值,表示该对象与o对象的大小关系,可以根据hot进行比较,将hot值大的对象排在前面。
*/
@Override
public int compareTo(Movie o) {
    return o.hot-this.hot;
}

?由于Movie类实现了WritableComparable接口,并实现了compareTo()方法,因此在Reduce阶段会根据Movie对象的hot属性进行排序。

 

###Kmeans:

  1.对于一些有明显划分逻辑的类不需要聚类,例如经纬度--->城市,年龄段---->少年,青年,中年,老年.

  2.什么时候需要去聚类:我们没有先验信息,去对数据做划分.

  3.map阶段:计算每个样本到聚类中心的聚类,然后进行比较距离大小(根据业务场景,需对距离进行归一化操作或者标准化),将样本划分到一个簇中.

  4.reduce阶段:计算新的聚类中心.

  5.Driver阶段:进行迭代.

[代码]

KmeansMap.java

 1 public class KmeansMap extends Mapper<LongWritable, Text, Text, Text> {
 2     List<ArrayList<String>> centers;
 3     //setup()方法加载预置文件,提前把数据加载进来
 4     /*
 5     在setup()方法中,通过调用Util.getCenterFile()方法获取了预置的初始聚类中心,
 6     并将其存储在List<ArrayList<String>>类型的centers变量中。打印出centers变量便于检查是否成功读取聚类中心数据。
 7      */
 8     @Override
 9     protected void setup(Context context) throws IOException, InterruptedException {
10         super.setup(context);
11        centers= Util.getCenterFile(DataSource.old_center+"/part-r-00000");
12 //1,0.5483598064494786,0.5620634207706902,0.631409127357256
13 //2,0.3097815453182178,0.30933422990614073,0.2494839735711083
14 //3,0.10216864838289068,0.056547788616962635,0.0982705121497766
15 //4,0.8526661438398866,0.8877750526698496,0.8710574668862512
16 //5,0.9282123373771547,0.8518260893453832,0.8992078636901775
17        System.out.println(centers+"啊哈哈哈哈哈哈哈");
18     }
19     //每个block块对应的map都有老聚类中心
20     public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
21 //     step 1。读取一行数据
22         String data = value.toString();
23 //      step2_similarity 数据切分获得元组
24         String[] tmpSplit = data.split(",");
25 //      step3_ItemUser 申请一个字符列表
26         List<String> parameter = new ArrayList<>();
27 //      step4_predictScore 塞入一条数据 此时paramter第一条数据 第一个位置是用户id
28         for (int i = 0; i < tmpSplit.length; i++) {
29             parameter.add(tmpSplit[i]);
30         }
31 
32 // step5_recommand_item 读取聚类中心点文件(其中路径参数是从命令中获取的)
33 
34         // step6 计算目标对象到各个中心点的距离,找最大距离对应的中心点,则认为此对象归到该点中
35         String outKey="" ;// 默认聚类中心为0号中心点
36         double minDist = Double.MAX_VALUE;
37 //        遍历5个聚类中心
38         for (int i = 0; i < centers.size(); i++) {
39             double dist = 0;
40 //        计算一个样本跟一个聚类中心的距离
41             for(int j=1;j<centers.get(i).size();j++){
42                 double a=Double.parseDouble(parameter.get(j)); //parameter原始数据
43                 double b=Double.parseDouble(centers.get(i).get(j));
44                 dist+=Math.pow(a-b,2);
45 
46             }
47             if (dist < minDist) {
48 //              outKey = centers.get(i).get(0);// 类编号  outKey = String.valueOf(i)
49                 outKey = String.valueOf(i);
50                 minDist = dist;
51             }
52 
53         }
54         //value是每一行数据
55         System.out.println(value+"属于:"+outKey+"类");
56 
57 //        1,【0.5997964273824741,0.5381577086398976,0.630918833446788】
58 //        1,【0.5477960351179503,0.6806860650336256,0.671837028690025】
59         context.write(new Text(outKey), value);
60     }
61 }

KmeansReduce.java

 1 public class KmeansReduce extends Reducer<Text, Text, Text,Text> { //计算新的聚类中心
 2     public void reduce(Text key, Iterable<Text> value, Context context) throws IOException,InterruptedException{
 3 //        1 ,[[0.5997964273824741,0.5381577086398976,0.630918833446788],[0.6425046619670638,0.5793500048314731,0.6036807346762254],[0.6147865482689907,0.6099377890937322,0.5681208931012612]]
 4 
 5         //key为类编号,value为类内所有样本,Iterable<Text> value,海量数据,类少的情况下 value是海量的
 6         //combiner可以用到,
 7         long num=0;
 8 //        顶以一个k维度数,存储累加结果
 9         double[] re=new double[DataSource.feat_num];
10 
11 //        遍历value计算累加值,并标记样本个数,
12         for(Text T:value){
13             num++;//计算一个簇中样本个数
14             String onePoint=T.toString(); //T=[0.5997964273824741,0.5381577086398976,0.630918833446788]变成字符串
15             onePoint=onePoint.replace(",", " ");
16             String[] parameters=onePoint.split(" ");//0.5997964273824741 0.5381577086398976 0.630918833446788
17             //进行累加,此处仅有两个元素,如果多个元素该如何
18             for (int i = 1; i <parameters.length ; i++) {
19                 re[i-1]+=Double.parseDouble(parameters[i]); //每列进行相加
20             }
21 
22         }
23         String result=key.toString()+",";
24         for (int i = 0; i <re.length ; i++) {
25             result=result+re[i]/num+","; //算均值,然后类和值的字符串拼接
26         }
27         //将每个均值添加到result字符串中,并在其后添加逗号。最后,
28         // 使用字符串截取方法substring去掉最后一个多余的逗号,即 result.length()-1,
29         // 从而得到最终的字符串表示该簇的类和均值。
30         result.substring(0,result.length() -1);
31         context.write(key,new Text(result));
32     }
33 }

KmeansDriver.java

 1 package hadoop_test.kmeans_demo_13;
 2 
 3 import org.apache.hadoop.conf.Configuration;
 4 import org.apache.hadoop.fs.FileSystem;
 5 import org.apache.hadoop.io.Text;
 6 import org.apache.hadoop.mapreduce.Job;
 7 import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
 8 import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
 9 
10 public class KmeansDriver {
11     // 主函数
12     public static void main(String[] args) throws Exception {
13         int repeats = 0;
14         //进行迭代
15         do {
16             //每一轮都需要构造conf
17             Configuration conf = new Configuration();
18             // 新建MapReduce作业并指定作业启动类
19             Job job = new Job(conf);
20             // 设置输入输出路径(输出路径需要额外加判断)
21             job.setJarByClass(KmeansDriver.class);
22             //1.输入数据路径
23             FileInputFormat.addInputPath(job, DataSource.inputpath);// 设置输入路径(指的是文件的输入路径)
24 //
25             FileSystem fs = DataSource.newCenter.getFileSystem(conf);
26 
27             if (fs.exists(DataSource.newCenter)) {// 设置输出路径(指的是中心点的输出路径)
28                 fs.delete(DataSource.newCenter, true);
29             }
30 
31             FileOutputFormat.setOutputPath(job, DataSource.newCenter);
32             // 为作业设置map和reduce所在类
33             job.setMapperClass(KmeansMap.class);
34             job.setReducerClass(KmeansReduce.class);
35             // 设置输出键和值的类
36             job.setOutputKeyClass(Text.class);
37             job.setOutputValueClass(Text.class);
38             // 启动作业
39             job.waitForCompletion(true);
40             repeats++;
41             //迭代停止条件:1.迭代次数,2.聚类中心聚类阈值
42         } while (repeats < DataSource.REPEAT && (Util.isStop(DataSource.old_center+"/part-r-00000",
43 
44                 DataSource.new_center+"/part-r-00000", repeats, DataSource.threshold)));
45 
46         // 进行最后的聚类工作(由map来完成)第二个map
47         Configuration c_conf = new Configuration();
48         // 新建MapReduce作业并指定作业启动类
49         Job c_job = new Job(c_conf);
50         // 设置输入输出路径(输出路径需要额外加判断)
51         FileInputFormat.addInputPath(c_job, DataSource.inputpath);// 设置输入路径(指的是文件的输入路径)
52         FileSystem fs = DataSource.newCenter.getFileSystem(c_conf);// 设置输出路径(指的是中心点的输出路径)
53         if (fs.exists(DataSource.newCenter)) {
54             fs.delete(DataSource.newCenter, true);
55         }
56         FileOutputFormat.setOutputPath(c_job, DataSource.newCenter);
57         // 为作业设置map(没有reducer,则看到的输出结果为mapper的输出)
58         c_job.setMapperClass(KmeansMap.class);
59         // 设置输出键和值的类
60         c_job.setOutputKeyClass(Text.class);
61         c_job.setOutputValueClass(Text.class);
62         // 启动作业
63         c_job.waitForCompletion(true);
64     }
65 }

 

posted on 2023-05-28 13:57  小吴要努力  阅读(24)  评论(0)    收藏  举报