内容:
假设一个年级有两个班级,数据分别在class1.csv和class2.csv中,求该年级的数学成绩平均值。数据第一列为学号,第二列为数学成绩。 要求,必须使用Combiner类,且最后输出一行数据,该行仅有一个平均值。
结果
(一)自定义Writable
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.Writable;
public class TxtAVG implements Writable{
private int count;
private double average;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public double getAverage() {
return average;
}
public void setAverage(double average) {
this.average = average;
}
public void readFields(DataInput in)throws IOException{
count = in.readInt();
average = in.readDouble();
}
public void write(DataOutput out)throws IOException{
out.writeInt(count);
out.writeDouble(average);
}
public String toString(){
return count+"\t"+average;
}
}
(二)MapReduce程序
import java.io.IOException;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.io.*;
public class WordCount {
public static class AvgMapper extends Mapper<Object, Text, Text, TxtAVG> {
private TxtAVG w = new TxtAVG();
@Override
protected void map(Object key, Text value, Context context)throws IOException, InterruptedException {
StringTokenizer tokenizer = new StringTokenizer(value.toString(), "\n");
while (tokenizer.hasMoreTokens()) {
String line = tokenizer.nextToken();
StringTokenizer tk = new StringTokenizer(line.toString(), ",");
//获取学号
String idString = tk.nextToken().toString();
//获取年级
String grade_class = idString.substring(0, 4);
//获取成绩
String scoreString = tk.nextToken().toString();
w.setCount(1);
w.setAverage(Integer.parseInt(scoreString));
context.write(new Text(grade_class), w);
}
}
}
public static class AvgReducer extends Reducer<Text, TxtAVG, Text, TxtAVG>
{
private TxtAVG r = new TxtAVG();
@Override
protected void reduce(Text key, Iterable<TxtAVG> values,Context context)throws IOException, InterruptedException {
int sum = 0;
int count = 0;
for (TxtAVG val : values) {
System.out.println(val);
sum += val.getCount()*val.getAverage();
count += val.getCount();
}
r.setCount(count);
r.setAverage(sum / count);
context.write(new Text(key.toString()), r);
System.out.println();
}
}
public static void main(String[] args) throws Exception {
String inputpath="hdfs://localhost:9000/Ainput";
String outputpath="hdfs://localhost:9000/Aoutput";
args = new String[] {inputpath,outputpath};
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
job.setJarByClass(WordCount.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(TxtAVG.class);
job.setMapperClass(AvgMapper.class);
job.setCombinerClass(AvgReducer.class);
job.setReducerClass(AvgReducer.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
job.waitForCompletion(true);
}
}
(三)实验结果
(1)创建存储计算源文件的目录
(2)把需要进行计算的文件上传到hdfs上,查询是否上传成功以及某个文件的内容:
(3)设置第一个文件形式
(4)设置第二个文件的形式
(5)经过combiner后得出的结果
(6)运行后,hdfs上的结果查询以及最终算出的平均数
结论
(1)计算键key与学生的学号有关的数据。开始并不知道怎么才能从学号提取关键的key,通过查询知道有两种方式,是split函数和StringTokenizer类,知道两者区别分别是:
①split函数用分割符拆分时, 如果是空字符串,就会输出文字串;StringTokenizer会把空字符串去掉。
②拆分方式:split 是使用正则表达式来拆分,
StringTokenizer是以文字串拆分。
(2)本来以为所谓的combiner形式仅是和reducer那边相同,开始只提取学号来计算,虽然能计算两个班级的平均分;但是第二步的reducer键值成为一个问题,查询网上使用两个mapreduce工作,但是有觉得很麻烦,而且对于两个job的设置不会。
(3)这次的平均值计算有两种方法:
①提取年纪即前四位数来作为key键,设map、combiner、reduce来计算,不过得设置三个的输入以及输出一一对应,否则会出错。
②自定义一个Writable,存储values的格式,这里采用的与课本的格式相同,mapreduce中只有map和reduce端。
(4)通过这次实验,何为hdfs文件系统以及与map、reduce、job的关系,还有就是combiner的作用。Combiner可以说是一个中介,把map传来的数据先进行初步处理,再传到reduce那边,否则如果是海量的数据,reduce的效率会非常低。Job相当一个启动器,把mapreduce程序和hdfs文件系统连接起来,即达到计算与存储分开。
(5)由于代码能力有限,用了很长时间才能实现这个计算,可见自己平时有多疏于代码,希望平时多敲代码。这次实验还有遗留两个问题:
①计算平均值,可能遇到小数问题,我虽然用了double类型,但不能达到我想要保留几位小数的要求;
②hadoop用的是key,value格式进行计算,传入的是这种格式,传出的也是,然后写到文件中就也是这种形式了。如何才能达到写入指定的数据,即如果不要key或是在value自定义设置的一些东西怎么取消,再存入呢?