自定义输出和输入类型介绍
我们在使用MapReduce处理需要两次聚合的数据时,我们会进行两次输出,第二次输出的结果是读取第一次输出的结果进程聚合处理的,但我们只需要看到第二次的聚合的结果就可以了,第一次聚合的结果我们是否能看懂都无所谓,
此时我们在进行第一次输出时,我们可以将输出类型由原来(当我们未自定义时默认的输入和输出类型都是TextInputFormat和TextOutputFormat)改为SequenceFileOutputFormat,Sequence相对于Text类型处理速度较快,且更节约内存,然后第二次读取的时候,输入类型也设定为SequenceFileInputFormat,但注意第二次聚合时map端的输入范型应该为和第一次聚合后输出的类型一致,即第一次输出的什么key和value,第二次接收的就是什么key和value
代码实现
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
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.input.FileSplit;
import org.apache.hadoop.mapreduce.lib.input.SequenceFileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;
import java.io.IOException;
import java.util.*;
public class Word {
/**
* Mapper类,读取文件,每次读一行
* 最后以文件名和单词进行拼接作为key,数量为value,输出
*/
static class WordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable> {
String fileName ;
Text k = new Text();
IntWritable v = new IntWritable(1);
@Override//setup方法,该方法会在map方法循环执行前,被执行一次
protected void setup(Context context) throws IOException, InterruptedException {
//获取读取的文件对象
FileSplit file = (FileSplit)context.getInputSplit();
//获取到文件的路径和文件名
fileName = file.getPath().getName();
}
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
try {//将切割流程try起来,避免脏数据影响程序运行
String s = value.toString();
//以空格切割,将每行的单词分开
String[] split = s.split("\\s+");
//数组内每个元素都是一个单词
for (String s1 : split) {
//将每个单词,和他的文件名进行拼接成K,输出到缓存区
String fileWord = s1+"-"+fileName;
k.set(fileWord);
context.write(k,v);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Reduce类,将分区排序后的数据进行聚合后输出
*/
static class WordCountReduce extends Reducer<Text, IntWritable,Text, IntWritable> {
IntWritable v = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int count = 0;
for (IntWritable value : values) {
//将迭代器中的1进行叠加聚合
count++;
}
v.set(count);
context.write(key,v);
}
}
/**
* 将第一次处理后的文件再读一次,进行二次聚合
* 第一次聚合后输出的文件类型为sequence类型的,所以接收时,要与上次输出的类型保持一致
*/
static class WordCountMapper2 extends Mapper<Text,IntWritable,Text,Text>{
Text k = new Text();
Text v = new Text();
@Override
protected void map(Text key, IntWritable value, Context context) throws IOException, InterruptedException {
try {
String s = key.toString()+" "+value.toString();
//用"-"将单词和文件名切开
String[] split = s.split("-");
//以单词为key,文件名和数量为value输出到缓存区
k.set(split[0]);
v.set(split[1]);
context.write(k,v);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 将缓存区内分区排序后的数据进行聚合运算
*/
static class WordCountReduce2 extends Reducer<Text,Text,Text,Text>{
Text v = new Text();
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
Map<String, Integer> map = new HashMap<>();
//将按单词,分组后的文件名加数量的迭代器遍历放入map集合中
for (Text value : values) {
String s = value.toString();
String[] split = s.split("\\s");
map.put(split[0],Integer.parseInt(split[1]));
}
System.out.println(map.size());
//将map集合转换成set单链集合
Set<Map.Entry<String, Integer>> entries = map.entrySet();
//将set集合转换为list集合
ArrayList<Map.Entry<String, Integer>> list = new ArrayList<>(entries);
//对map集合的value逆序排序
list.sort(new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o2.getValue()-o1.getValue();
}
});
StringBuilder sb = new StringBuilder();
//遍历排序后的list集合,放入key和value中输出
for (Map.Entry<String, Integer> l : list) {
sb.append(l.getKey()+"-"+l.getValue()+" ");
//去除sb的最后一个空格
String vs = sb.toString().trim();
String vv = sb.toString();
v.set(vv);
}
context.write(key,v);
}
}
/**
* 启动类
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "m");
//第一次聚合的map端和reduce端
//job.setMapperClass(WordCountMapper.class);
//job.setReducerClass(WordCountReduce.class);
//第二次聚合的map端和reduce端
job.setMapperClass(WordCountMapper2.class);
job.setReducerClass(WordCountReduce2.class);
//当map和reduce类输出的类型相同时,可以省略以下两句
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//设置第一次输出的文件类型为sequence类型的
//job.setOutputFormatClass(SequenceFileOutputFormat.class);
//第一次聚合的输入和输出路径
//FileInputFormat.setInputPaths(job,new Path("D:\\txt\\mrdata\\index\\input"));
//FileOutputFormat.setOutputPath(job,new Path("D:\\txt\\mrdata\\index\\output20"));
//设置第二次输入的文件类型为sequence类型的
job.setInputFormatClass(SequenceFileInputFormat.class);
//第二次聚合的输入和输出路径
FileInputFormat.setInputPaths(job,new Path("D:\\txt\\mrdata\\index\\output26"));
FileOutputFormat.setOutputPath(job,new Path("D:\\txt\\mrdata\\index\\output27"));
job.waitForCompletion(true);
}
}