三-中上, 大数据基础架构Hadoop- Hadoop序列化概述和案例实操 hf
二, Hadoop序列化
2.1 序列化概述
[什么是序列化 ?]
-
序列化 就是把
内存中的对象, 转换为字节序列
(或其他数据传输协议)以便于存储到磁盘(持久化)和网络存储. -
反序列化 就是将收到的字节序列(或其他数据传输协议)或者是磁盘的持久化数据,
转换为内存中的对象
.
简而言之就是, 序列化是把对象转化为可传输的字节序列, 反序列化是把可传输的字节序列转化为对象.
[为什么要序列化 ?]
- 一般来说, "活的"对象只生存在内存中, 关机断电就没有了. 而且, "活的"对象只能由本地的进程使用, 不能发送到网络上的另外一台计算机. 然而序列化可以存储"活的"对象, 可以把"活的"对象发送到远程计算机.
[为什么不使用Java的序列化 ?]
- Java的序列化是一个重量级序列化框架(Serializable), 一个对象被序列化后, 会附带很多额外的信息(各种校验信息, Header, 继承体系等), 不便于在网络中高效传输. 所有Hadoop自己开发了一套序列化机制(Writable).
Hadoop序列化特点:
- 紧凑: 高效使用存储空间(附加信息少, 比如相比Java序列化只需要简单的校验信息)
- 快速: 读写数据的额外开销小(紧凑,所有传输开销小)
- 互操作: 支持多语言的交互(这边Java序列化后, 接收方可用C,C++之类的反序列化)
2.2 自定义bean对象实现序列化接口(Writable)
首先我们看一下Writable接口的源码
在企业开发中, 往往常用的基本序列化类型不能满足所有需求, 比如在Hadoop框架内部传递一个bean对象(相当于实体类),那么该对象需要实现序列化接口.
[具体实现 bean对象序列化的步骤如下: ]
- 必须实现Writable接口
- 重写序列化方法
write(DataOutput dataOutput)
- 重写反序列化方法
readFields(DataInput dataInput
- 反序列化时, 需要反射调用空参构造函数, 必须要有空参构造
- 注意反序列化的顺序和序列化的顺序完全一致
- 要想把结果显示在文件中(默认传输过来的是地址值), 需要重写toString(), 可用"\t"分开, 方便后续使用
- 如果需要将自定义的bean放在key中传输, 则还需要实现Comparable接口, 因为MapReduce框架中的Shuffle过程要求对key必须能排序.
注意: 对每一个属性序列化的顺序, 要跟反序列化的顺序一一对应,
先序列化的属性先被反序列化
.
2.3 序列化案例实操
[需求]
[需求分析]
[具体实现]
-
编写
FlowBean
类作为bean类(实体类), 其中包含了
需要序列化的私有属性,
公有的get和set方法,
无参的构造方法,
用于输出的toString(),
以及bean类不可或缺的序列化方法write()和反序列化方法readFiles; -
编写
FlowMapper
, 继承org.apache.hadoop.mapreduce.Mapper中的Mapper接口, 实现其Mapper方法, 在Mapper方法中,
KEYIN- 偏移量, VALUEIN- 文本中的一行,
KEYOUT- 手机号(有重复), VALUEOUT-bean类(包含需要序列化的属性, 上行, 下行, 总流量);map方法主要就是实现有效数据的提取和整理
-
编写
FlowReducer
, 继承org.apache.hadoop.mapreduce.Reducer中的Reducer接口, 实现其reduce方法, 在这个方法中,
KEYIN- 手机号(有重复), VALUEIN-bean类(包含需要序列化的属性, 上行, 下行, 总流量); ,
KEYOUT-手机号(无重复), VALUEOUT-bean类(对相同手机号的上行,下行, 总流量进行汇总)reduce方法主要完成对重复手机号的提取, 以及对重复手机号不同的上下行流量和总流量的汇总.
- 具体代码如下:
FlowBean
package writable;
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/*** Bean类(实体类)
* 1.实例化Writable接口
* 2.空参构造
* 3.get, set方法
* 4.重写序列化方法
* 5.重写反序列化方法
*/
public class FlowBean implements Writable {
//自定义属性
private long upFlow;
private long downFlow;
private long sumFlow;
//getter setter
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 setSumFlow(){
this.sumFlow = this.upFlow + this.downFlow;
}
//无参构造
public FlowBean(){
}
//toString
@Override
public String toString(){
return "上行流量"+upFlow+"\t"+"下行流量"+downFlow+"\t"
+"总流量: "+sumFlow;
}
//实现序列化和反序列化方法, 注意顺序要保持一致
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeLong(upFlow);
dataOutput.writeLong(downFlow);
dataOutput.writeLong(sumFlow);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
this.upFlow= dataInput.readLong();
this.downFlow= dataInput.readLong();
this.sumFlow= dataInput.readLong();
}
}
FlowMapper
package writable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class FlowMapper extends Mapper<LongWritable , Text , Text, FlowBean> {
Text outK = new Text();
FlowBean outV = new FlowBean();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//1. 分割字符串, 分隔后放入String数组
String line = value.toString();
String[] str = line.split("\t");
//2. 取出手机号, upflow, downflow
String phone = str[1];
String upFlow = str[str.length-3];
String downFlow = str[str.length-2];
//3. 把手机号转换为long类型, 封装到Text中
//把 upflow downflow转为long类型, 封装到flowbean中
outK.set(phone);
outV.setUpFlow(Long.parseLong(upFlow));
outV.setDownFlow(Long.parseLong(downFlow));
outV.setSumFlow();
//4. 写出
context.write(outK, outV);
}
}
FlowReducer
package writable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class FlowReducer extends Reducer<Text, FlowBean, Text, FlowBean> {
FlowBean outV = new FlowBean();
//注意, 仅仅在KEYIN(也就是Text类型的手机号)相同时候才调用reduce过程
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
long totalUp = 0;
long totalDown = 0;
//1. 对相同手机号的上行和下行分别相加
for (FlowBean value : values) {
totalUp += value.getUpFlow();
totalDown += value.getDownFlow();
}
//2. 把totalUp, totalDown写入到FlowBean类型的outV, 并更新down, up, sum的值
outV.setUpFlow(totalUp);
outV.setDownFlow(totalDown);
outV.setSumFlow();
//3.写出去
context.write(key, outV);
}
}
FlowDriver
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;
import java.io.IOException;
public class FlowDriver {
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
//1. 获取job
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
//2. 设置jar包(driver)的路径
job.setJarByClass(FlowDriver.class);
//3. 关联(Mapper)和(Driver)的路径
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
//4. 设置Mapper输出的key 和 value类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
//5. 设置最终输出的key和vlue 的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
//6.设置输入和输出的路径
FileInputFormat.setInputPaths(job, new Path("D:\\user\\flow\\input"));
FileOutputFormat.setOutputPath(job, new Path("D:\\user\\flow\\output"));
//7. 提交job
//verbose为true, 监控并打印信息
boolean res = job.waitForCompletion(true);
System.exit(res?0:1);
}
}
- 输出结果:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)