文章目录


一、序列化、反序列化概念

  • 序列化就是把内存中的对象,转成字节序列(或其他数据传输协议)以便于存出到磁盘(持久化)或网络传输​。
  • 反序列化就是将接收到字节序列(或其他数据传输协议)或者是磁盘持久化数据,转换成内存中的对象​。

返回顶部


二、为什么要序列化

一般来说,“活的”对象只生存在内存里,关机断电就没有了。且活的对象只能有本地的进程使用,不能被发送到网络上的另外一台计算机。然而序列化可以存储活的对象,可以将活的对象发送到远程计算机

简单的概括:使对象(带有数据的)可以传输!

返回顶部


三、为什么不使用java的序列化?

  • java的序列化是一个重量级序列化框架(Serializable),一个对象被序列化之后,会附带很多额外的信息(各种校验信息、Header、继承体等),​不便于在网络中高效传输​。所以,Hadoop自己开发了一套序列化机制(Writable).
  • Hadoop序列化特点
 - 紧凑 :高效使用存储空间
- 快速 :读写数据的额外开销小
- 可扩展性 :随着通信协议的升级而可升级
- 互操作性 :支持多语言的交互

返回顶部


四、自定义bean对象实现序列化接口

在企业开发中往往常用的基本序列化类型不能满足所有需求,比如在Hadoop框架中传递一个bean对象,那么该对象就需要实现序列化接口.

◆ bean对象序列化步骤


1.必须实现 Writable 接口

public class FlowBean implements Writable {
......
}

2.定义空参构造函数

  • 反序列化时,需要反射调用空参构造函数,所以必须定义空参构造函数
public FlowBean(){
super();
}

3.重写序列化方法

  • 包含对象所有的传输属性
@Override
public void write(DataOutput out) throws IOException {
out.writeLong(upFlow);
out.writeLong(downFlow);
out.writeLong(sumFlow);
}

4.重写反序列化方法

@Override
public void readFields(DataInput in) throws IOException {
upFlow = in.readLong();
downFlow = in.readLong();
sumFlow = in.readLong();
}

5. 保持顺序一致

  • 注意反序列顺序必须和​序列化顺序​一致

6. 重写toString()方法

  • 要想把结果展示在文件中,需要重写toString()方法,可用“\t”分开,方便后续使用

7. 实现Comparable接口

  • 如果需要将自定义的bean放在key中传输,则还需要实现Comparable接口,因为MR框架中的shuffle过程要求key必须能排序
@Override
public int compareTo(FlowBean o) {
// 倒序排列,从大到小
return this.sumFlow > o.getSumFlow() ? -1 : 1;
}

返回顶部


五、序列化案例 ---- 流量值统计

◆ 需求分析

  • 统计每一个手机号耗费的总上行流量、下行流量、总流量

【MapReduce】序列化_hadoop


◆ 数据集

已有数据集

【MapReduce】序列化_apache_02


【MapReduce】序列化_hadoop_03


目标输出样式

【MapReduce】序列化_hadoop_04


返回顶部


① 自定义bean对象阶段

import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

public class FlowBean implements Writable {

/**
* 上行流量是指你上传文件时的流量,比如你把自己电脑里的电影放到网络硬盘里时,会有一个上传流量
* 下行流量是指你下载文件时的流量,比如你下载一首歌曲时,会有一个下载流量
*/
private long upFlow; // 上行流量
private long downFlow; // 下行流量
private long sumFlow; // 总流量

/**
* 无参构造
*/
public FlowBean() {
super();
}

/**
* 有参构造
*/
public FlowBean(long upFlow, long downFlow) {
this.upFlow = upFlow;
this.downFlow = downFlow;
sumFlow = upFlow + downFlow;
}

/**
* 序列化方法
*
* @param dataOutput
* @throws IOException
*/
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeLong(upFlow);
dataOutput.writeLong(downFlow);
dataOutput.writeLong(sumFlow);
}

/**
* 反序列化方法
*
* @param dataInput
* @throws IOException
*/
@Override
public void readFields(DataInput dataInput) throws IOException {
upFlow = dataInput.readLong();
downFlow = dataInput.readLong();
sumFlow = dataInput.readLong();
}

/**
* 重写toString()
*/
@Override
public String toString() {
// 方便后续切割
return upFlow + "\t" + downFlow + "\t" + sumFlow;
}

/**
* get、set方法
*/
public long getUpFlow() {
return upFlow;
}

public void setUpFlow(long upFlow) {
this.upFlow = upFlow;
}

public long getDownFlow() {
return downFlow;
}

public void setDownFlow(long downFlow) {
this.downFlow = downFlow;
}

public long getSumFlow() {
return sumFlow;
}

public void setSumFlow(long sumFlow) {
this.sumFlow = sumFlow;
}
// 赋值、求和
public void set(long upFlow1,long downFlow1){
upFlow = upFlow1 ;
downFlow = downFlow1;
sumFlow = upFlow1 + downFlow1;
}
}

★ 注意

// 赋值、求和
public void set(long upFlow,long downFlow){
upFlow = this.upFlow ;
downFlow = this.downFlow;
sumFlow = upFlow + downFlow;
}

这是我一开始写的代码,最后运行的时候,流量值全是0。一开始没找到哪里出错,后来发现就是这里。在对象类中,我一开始定义了变量upFlow,downFlow,也​就是this.upFlow和this.downFlow​。讲到这里,就很清楚了,声明的变量并没有赋值,默认全为0,直接又赋值给了传进来的参数upFlow,downFlow,不就相当于调用该方法的时候没传参数,输出肯定是0。

写这个方法的目的是在于,将后面Reducer阶段的时候,将统计的结果赋值,最终好输出。所以应当是将最终的结果当做参数赋给一开始声明的变量。赋值是从右往左进行的,所以右边应为参数,左边为目标变量。

返回顶部


② Mapper阶段

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

public class FlowCountMapper extends Mapper<LongWritable, Text,Text,FlowBean> {

Text k = new Text();
FlowBean bean = new FlowBean();

@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//1 13736230513 192.196.100.1 www.atguigu.com 2481 24681 200

// 1. 获取第一行数据
String line = value.toString();

// 2. 分割 \t
String[] fields = line.split("\t");

// 3. 封装对象
k.set(fields[1]); // 手机号作为key
long upFlow = Long.parseLong(fields[fields.length-3]);
long downFlow = Long.parseLong(fields[fields.length-2]);
bean.setUpFlow(upFlow);
bean.setDownFlow(downFlow);
// 4. 写出
context.write(k,bean);
}
}

返回顶部


③ Reducer阶段

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;

public class FlowCountReducer extends Reducer<Text,FlowBean,Text,FlowBean> {
long sum_upFlow = 0;
long sum_downFlow = 0;
FlowBean bean = new FlowBean();
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {

// 1.累加求和
for (FlowBean flowBean:values){
sum_upFlow += flowBean.getUpFlow();
sum_downFlow += flowBean.getDownFlow();
}
bean.set(sum_upFlow,sum_downFlow);

// 2.写出
context.write(key,bean);
sum_downFlow = 0;
sum_upFlow = 0;
}
}

返回顶部


④ Driver阶段

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.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class FlowCountDriver {
public static void main(String[] args) {

Job job = null;
try {
// 1.获取job对象
Configuration conf = new Configuration();
job = Job.getInstance(conf);
// 2.设置jar路径
job.setJarByClass(FlowCountDriver.class);
// 3.关联 M R
job.setMapperClass(FlowCountMapper.class);
job.setReducerClass(FlowCountReducer.class);
// 4.设置 M\R 输入输出类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
// 5. 设置输入输出路径
FileInputFormat.setInputPaths(job,new Path("G:\\Projects\\IdeaProject-C\\MapReduce\\src\\main\\java\\第二章_Hadoop序列化\\dataset"));
FileOutputFormat.setOutputPath(job,new Path("G:\\Projects\\IdeaProject-C\\MapReduce\\src\\main\\java\\第二章_Hadoop序列化\\output"));
// 6. 提交job
Boolean result = job.waitForCompletion(true);
System.exit(result?0:1);
} catch (Exception e){
e.printStackTrace();
}
}
}

返回顶部


⑤ 运行

【MapReduce】序列化_apache_05

返回顶部