当输出是多个字段是,需要使用对象来表示,此时这个对象要序列化
序列化概念
Java 的序列化是一个重量级序列化框架(Serializable),一个对象被序列化后,会附带很多额外的信息(各种校验信息,Header,继承体系等),不便于在网络中高效传输。所以,Hadoop 自己开发了一套序列化机制(Writable)
在编写MapReduce程序时,我们会发现,对于MapReduce的输入输出数据(key-value),我们只能使用Hadoop提供的数据类型,而不能使用Java本身的基本数据类型,比如,如果数据类型为long,那么在编写MR程序时,对应Hadoop的数据类型则为LongWritable。关于原因,简单说明如下:hadoop在节点间的内部通讯使用的是RPC,RPC协议把消息翻译成二进制字节流发送到远程节点,
远程节点再通过反序列化把二进制流转成原始的信息。也就是说,传递的消息内容是需要经过hadoop特定的序列化与反序列化操作的,因此,才需要使用hadoop提供的数据类型,当然,如果想要自定义MR程序中key-value的数据类型,则需要实现相应的接口,如Writable、WritableComparable接口
也就是说,如果需要自定义key-value的数据类型,必须要遵循如下的原则:
/**
* MapReduce的任意的key和value都必须要实现Writable接口
* MapReduce的任意key必须实现WritableComparable接口,WritableComparable是Writable的增强版
* key还需要实现Comparable的原因在于,对key排序是MapReduce模型中的基本功能
*/
常用数据类型对应的 Hadoop 数据序列化类型
自定义序列化数据类型的步骤
(1)必须实现Writable接口
(2)反序列化时,需要反射调用空参构造函数,所以必须有空参构造
(3)重写序列化方法
(4)重写反序列化方法
(5)注意反序列化的顺序和序列化的顺序完全一致
(6)要想把结果显示在文件中,需要重写 toString(),可用 \t 分开,方便后续用
(7)如果需要将自定义的 bean 放在 key 中传输,则还需要实现Comparable 接口,因为 MapReduce 框中的 Shuffle 过程要求对 key 必须能排序
在MapReduce中,要求被序列化的对象对应的类中必须提供无参构造在MapReduce中,要求被序列化的对象的属性值不能为null
案例统计每个手机号的上下行流量
手机号,上行流量,下行流量 peopleinfo.txt
1880349000110,4321,7000
1880349000111,4121,6000
1880349000112,4021,3000
1880349000110,2321,9000
1880349000112,3000,4000
上传到服务器上,这里在idea里安装插件 hdfs big data tools工具完成上传
编写代码
Mapper
package flow;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
// 用于完成Map阶段
// 再MapReduce中,要求被处理的数据能够被序列化
// MApReduce提供了一套单独的序列化机制
// KEYIN-输入的键的类型。如果不指定,那么默认情况下,表示行的字节偏移量
// VALUEIN-输入值得类型。如果不指定,那么默认情况下,表示的读取到的一行数据
// KEYOUT-输出的键的类型。当前案例中,输出的键表示的是手机号
// VALUEOUT-输出的值的类型。当前案例,输出的值表示的是上下行流量
public class FlowMapper extends Mapper<LongWritable, Text, Text,PeopleInfo> {
// 覆盖map方法,将处理逻辑写到这个方法中
// key:键。表示的是行的字节偏移量
// value:值。表示读取到的一行数据
// context:配置参数
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] array = value.toString().split(",");
PeopleInfo p = new PeopleInfo();
p.setUpFlow(Integer.parseInt(array[1]));
p.setDownFlow(Integer.parseInt(array[2]));
context.write(new Text(array[0]),p);
}
}
Reducer
package flow;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
// KEYIN、VALUEIN输入的键的类型。
// Reducer的数据从Mapper来的
// 所以Mapper的输出就是Reducer的输入
// KEYOUT、VALUEOUT-输出的值的类型。当前案例中,要输出每一个手机号对应的上下行流量
public class FlowReducer extends Reducer<Text, PeopleInfo,Text,Text> {
protected void reduce(Text key, Iterable<PeopleInfo> values, Context context) throws IOException, InterruptedException {
int sumup = 0;
int sumdown = 0;
for (PeopleInfo p : values) {
sumup+= p.getUpFlow();
sumdown+= p.getDownFlow();
}
//3、写入上下文
context.write(key,new Text("上行流量:"+sumup+"下行流量:"+sumdown));
}
}
Driver
package flow;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
import java.io.IOException;
public class JobMain {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//一、初始化Job
Configuration configuration = new Configuration();
//获取运行命令的参数,参数一:输入文件路径,参数二:输出文件路径
//如果输入路径是一个文件,那么只处理这个文件,如果指定的路径是目录,则处理这个目录下的所有文件
//输出路径只能是不存在的目录名
String [] otherArgs = new GenericOptionsParser(configuration,args).getRemainingArgs();
if(otherArgs.length < 2){
System.err.println("必须提供输入文件路径和输出文件路径");
System.exit(2);
}
Job job = Job.getInstance(configuration, "mr");
job.setJarByClass(JobMain.class);
//二、设置Job的相关信息 8个小步骤
//1、设置输入路径
job.setInputFormatClass(TextInputFormat.class);
//本地运行
//TextInputFormat.addInputPath(job,new Path("/tmp/input/mr1.txt"));
TextInputFormat.addInputPath(job,new Path(args[0]));
//2、设置Mapper类型,并设置输出键和输出值
job.setMapperClass(FlowMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(PeopleInfo.class);
//shuffle阶段,使用默认的
//3、设置Reducer类型,并设置输出键和输出值
job.setReducerClass(FlowReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//4、设置输出路径
job.setOutputFormatClass(TextOutputFormat.class);
//本地运行
//TextOutputFormat.setOutputPath(job,new Path("/tmp/output/mr"));
TextOutputFormat.setOutputPath(job,new Path(args[1]));
//三、等待完成
boolean b = job.waitForCompletion(true);
System.out.println(b==true?"MapReduce 任务执行成功!":"MapReduce 任务执行失败!");
System.exit(b ? 0 : 1);
}
}
对象实体
package flow;
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class PeopleInfo implements Writable {
private int upFlow;
private int downFlow;
public int getDownFlow() {
return downFlow;
}
public void setDownFlow(int downFlow) {
this.downFlow = downFlow;
}
public int getUpFlow() {
return upFlow;
}
public void setUpFlow(int upFlow) {
this.upFlow = upFlow;
}
// 需要将有必要的属性依次序列化写出即可
@Override
public void write(DataOutput out) throws IOException {
out.writeInt(getUpFlow());
out.writeInt(getDownFlow());
}
@Override
public void readFields(DataInput in) throws IOException {
setUpFlow(in.readInt());
setDownFlow(in.readInt());
}
}
打成jar包上传
执行
hadoop jar /hadoopmapreduce-1.0-SNAPSHOT.jar flow.JobMain /peopleinfo.txt /mypeopleinfo
浏览输出文件内容
hadoop fs -cat /mypeopleinfo/part-r-00000