数据类型与自定义数据类型
- 1、Hadoop中的数据类型
- 为什么不用 Java中类型?
- 如果我的需求中,需要对多列数据进行处理怎么办?
- 解决方案一:
- 解决方案二:
- 2、自定义数据类型
- ==方式一:构建一个JavaBean实现Writable接口==
- ==方式二:构建一个JavaBean实现WritableComparable接口==
- 区别
- 3、总结
- 为什么要自定义数据类型?
- 如何自定义数据类型?
- 方式一:实现Writable接口
- 方式二:实现WritableComparable接口
- 两种方式之间有什么区别?
1、Hadoop中的数据类型
在MR中必须使用封装好的类型来定义KV
为什么不用 Java中类型?
因为Hadoop是分布式程序,所有的数据类型都要经过网络传输,必须实现序列化与反序列化
- 举例:node01:String age = “1”;将这个对象传递给第二台机器
- 不做序列化:传递的是数据
- node01 -> 1 -> node02
- node02只知道这个数据是1,不知道类型
- 做了序列化:传递的是对象【对象的信息和数据】
- node01 ->序列化: String age=“1” -> node02
- node02知道数据为1,并且是String类型
- node02拿到node01传输过来的数据要进行反序列化
Java中常见的类型都没有实现序列化与反序列化
- Hadoop中提供了基于Java类型 封装的很多序列化类型
- IntWritable:int
- Text:String
- LongWritable:long
- NullWritable
- DoubleWritable:double
- ……
基于Java的类型封装构建了JavaBean,添加序列化与反序列化的支持
整个MapReduce中所有的数据都以KV形式存在,数据最多只能有两列,K一列,V一列
如果我的需求中,需要对多列数据进行处理怎么办?
- 需求:将每个单词的长度随着的单词统计的结果一起输出
hadoop 6 4
hbase 5 3
hive 4 2
spark 5 1
解决方案一:
- Text:通过Text类型拼接
- 假设有100列,50列拼接成K,另外50列拼接为V
- 不太可行:列太多了,代码非常麻烦,无法对每一列进行处理
- 适合于列比较少的场景
- new Text(word+“\t”+word.length),这个整体作为K
解决方案二:
- 利用Hadoop提供的自定义数据类型的接口实现自定义封装JavaBean
2、自定义数据类型
方式一:构建一个JavaBean实现Writable接口
- 定义属性
- 重写方法
- 构造(
必须有空参构造
,因为反序列化时反射调用空参构造函数
) - get and set方法
- 序列化和反序列化(顺序完全一致)
- toString方法("\t"隔开,方便后续使用)
- 实现
package bigdata.hanjiaxiaozhi.cn.mapreduce.userbean;
import com.sun.corba.se.spi.ior.Writeable;
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* @ClassName WordCountBean1
* @Description TODO 实现封装单词和单词的长度的JavaBean,用于Hadoop中的KV
* @Date 2020/5/30 15:29
* @Create By hanjiaxiaozhi
*/
public class WordCountBean1 implements Writable {
//定义属性
private String word;
private int length;
//构造
public WordCountBean1(){
}
//统一的赋值方法
public void setAll(String word,int length){
this.setWord(word);
this.setLength(length);
}
//get and set
public String getWord() {
return word;
}
public void setWord(String word) {
this.word = word;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
//序列化方法
@Override
public void write(DataOutput out) throws IOException {
//将word进行序列化
out.writeUTF(this.word);
//将length进行序列化
out.writeInt(this.length);
}
//反序列化:顺序必须与序列化的顺序是一致的
@Override
public void readFields(DataInput in) throws IOException {
//读取word,进行反序列化
this.word = in.readUTF();
//读取length,进行反序列化
this.length = in.readInt();
}
//toString方法:将对象打印成字符串
@Override
public String toString() {
return word+"\t"+length;
}
}
- 程序
package bigdata.hanjiaxiaozhi.cn.mapreduce.wc;
import bigdata.hanjiaxiaozhi.cn.mapreduce.userbean.WordCountBean1;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import java.io.IOException;
/**
* @ClassName WCDriver
* @Description TODO 实现WordCount,结果有三列
* 单词 长度 个数
* hadoop 6 4
* hbase 5 3
* hive 4 2
* spark 5 1
*
* @Date 2020/5/30 11:30
* @Create By hanjiaxiaozhi
*/
public class WCUserBea extends Configured implements Tool {
@Override
public int run(String[] args) throws Exception {
//创建一个job
Job job = Job.getInstance(this.getConf(),"wctest");
job.setJarByClass(WCUserBea.class);
//配置job
Path inputPath = new Path("datas/wordcount/input");
TextInputFormat.setInputPaths(job,inputPath);
job.setMapperClass(WCMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setReducerClass(WCReducer.class);
job.setOutputKeyClass(WordCountBean1.class);
job.setOutputValueClass(IntWritable.class);
Path outputPath = new Path("datas/output/wordcount/output4");
TextOutputFormat.setOutputPath(job,outputPath);
//提交运行
boolean result = job.waitForCompletion(true);
return result ? 0 : -1;
}
public static void main(String[] args) throws Exception {
//构建一个Configuration
Configuration conf = new Configuration();
//调用当前类的run方法
int status = ToolRunner.run(conf, new WCUserBea(), args);
//退出
System.exit(status);
}
public static class WCMapper extends Mapper<LongWritable, Text,Text, IntWritable>{
Text outputKey = new Text();
IntWritable outputValue = new IntWritable(1);
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//从value中取出整行,并分割,得到每个单词
String[] words = value.toString().split(" ");
//将每个单词取出
for (String word : words) {
//将单词作为key,value恒为1
this.outputKey.set(word);
//输出
context.write(this.outputKey,this.outputValue);
//输出2:不建议这么做,每条数据会调用一次map方法,会导致创建非常多的对象
// context.write(new Text(word),new IntWritable(1));
}
}
}
public static class WCReducer extends Reducer<Text, IntWritable, WordCountBean1, IntWritable>{
WordCountBean1 outputKey = new WordCountBean1();
IntWritable outputValue = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
//将当前单词出现的所有次数累加
for (IntWritable value : values) {
sum += value.get();
}
//将最后累加的个数作为value
this.outputValue.set(sum);
//将单词以及长度封装为key
this.outputKey.setWord(key.toString());
this.outputKey.setLength(key.toString().length());
//输出:会调用toString方法
context.write(this.outputKey,this.outputValue);
}
}
}
方式二:构建一个JavaBean实现WritableComparable接口
- 定义属性
- 重写方法
- 构造(必须有空参构造,因为反序列化时,反射调用空参构造函数)
- get and set方法
- 序列化和反序列化(顺序完全一致)
- toString方法("\t"隔开,方便后续使用)
- compareTo方法
- 需求:希望这个长度从Map阶段就有,在Map处理单词的时候就添加这个长度,Map输出的结果
- 原来的Map输出
hadoop 1
hive 1
hbase 1
hive 1
hadoop 1
hadoop 1
spark 1
hbase 1
hbase 1
hadoop 1
- 希望的Map输出
hadoop 6 1
hive 4 1
hbase 5 1
hive 4 1
hadoop 6 1
hadoop 6 1
spark 5 1
hbase 5 1
hbase 5 1
hadoop 6 1
- 单词和长度作为一个整体是Key,1是value
- shuffle:分组,排序
key value
hadoop 6 1,1,1,1
hbase 5 1,1,1
hive 4 1,1
spark 5 1
- reduce:迭代器的聚合
key value
hadoop 6 4
hbase 5 3
hive 4 2
spark 5 1
- 常见报错
类型不匹配
- Driver中定义的Map和Reduce的KV类型与实际Map和Reduce中的类型不一致
- 自定义的类型如果作为Map输出的Key需要经过排序,必须实现WritableComparable接口
- Shuffle阶段会有排序:默认按照Key排序
- 如果Key是用户自定义的类型,必须指定排序的方法
- 必须实现WritableComparable接口
- 实现
package bigdata.hanjiaxiaozhi.cn.mapreduce.userbean;
import com.sun.corba.se.spi.ior.Writeable;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* @ClassName WordCountBean1
* @Description TODO 实现封装单词和单词的长度的JavaBean,用于Hadoop中的KV
* @Date 2020/5/30 15:29
* @Create By hanjiaxiaozhi
*/
public class WordCountBean1 implements WritableComparable<WordCountBean1> {
//定义属性
private String word;
private int length;
//构造
public WordCountBean1(){
}
//统一的赋值方法
public void setAll(String word,int length){
this.setWord(word);
this.setLength(length);
}
//get and set
public String getWord() {
return word;
}
public void setWord(String word) {
this.word = word;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
//序列化方法
@Override
public void write(DataOutput out) throws IOException {
//将word进行序列化
out.writeUTF(this.word);
//将length进行序列华
out.writeInt(this.length);
}
//反序列化:顺序必须与序列化的顺序是一致的
@Override
public void readFields(DataInput in) throws IOException {
//读取word,进行反序列化
this.word = in.readUTF();
//读取length,进行反序列化
this.length = in.readInt();
}
//toString方法:将对象打印成字符串
@Override
public String toString() {
return word+"\t"+length;
}
/**
* 如果自定义的类型作为Map输出的Key,需要在shuffle中 经过排序,就会调用这个方法来对这个类型排序
* @param o
* @return
*/
@Override
public int compareTo(WordCountBean1 o) {
//先比较第一个属性,单词是否相等
int comp = this.getWord().compareTo(o.getWord());
if(comp == 0){
//如果第一个属性相等,就比较第二个属性,第二个属性的比较结果作为最终的结果
return Integer.valueOf(this.getLength()).compareTo(Integer.valueOf(o.getLength()));
}else
return comp;
}
}
- 程序
package bigdata.hanjiaxiaozhi.cn.mapreduce.wc;
import bigdata.hanjiaxiaozhi.cn.mapreduce.userbean.WordCountBean1;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import java.io.IOException;
/**
* @ClassName WCDriver
* @Description TODO 实现WordCount,结果有三列
* 单词 长度 个数
* hadoop 6 4
* hbase 5 3
* hive 4 2
* spark 5 1
*
* @Date 2020/5/30 11:30
* @Create By hanjiaxiaozhi
*/
public class WCUserBea extends Configured implements Tool {
@Override
public int run(String[] args) throws Exception {
//创建一个job
Job job = Job.getInstance(this.getConf(),"wctest");
job.setJarByClass(WCUserBea.class);
//配置job
Path inputPath = new Path("datas/wordcount/input");
TextInputFormat.setInputPaths(job,inputPath);
job.setMapperClass(WCMapper.class);
job.setMapOutputKeyClass(WordCountBean1.class);
job.setMapOutputValueClass(IntWritable.class);
job.setReducerClass(WCReducer.class);
job.setOutputKeyClass(WordCountBean1.class);
job.setOutputValueClass(IntWritable.class);
Path outputPath = new Path("datas/output/wordcount/output5");
TextOutputFormat.setOutputPath(job,outputPath);
//提交运行
boolean result = job.waitForCompletion(true);
return result ? 0 : -1;
}
public static void main(String[] args) throws Exception {
//构建一个Configuration
Configuration conf = new Configuration();
//调用当前类的run方法
int status = ToolRunner.run(conf, new WCUserBea(), args);
//退出
System.exit(status);
}
public static class WCMapper extends Mapper<LongWritable, Text, WordCountBean1, IntWritable>{
WordCountBean1 outputKey = new WordCountBean1();
IntWritable outputValue = new IntWritable(1);
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//从value中取出整行,并分割,得到每个单词
String[] words = value.toString().split(" ");
//将每个单词取出
for (String word : words) {
//将单词作为key,value恒为1
this.outputKey.setWord(word);
this.outputKey.setLength(word.length());
//输出
context.write(this.outputKey,this.outputValue);
//输出2:不建议这么做,每条数据会调用一次map方法,会导致创建非常多的对象
// context.write(new Text(word),new IntWritable(1));
}
}
}
public static class WCReducer extends Reducer<WordCountBean1, IntWritable,WordCountBean1, IntWritable>{
// Text outputKey = new Text();
IntWritable outputValue = new IntWritable();
@Override
protected void reduce(WordCountBean1 key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
//将当前单词出现的所有次数累加
for (IntWritable value : values) {
sum += value.get();
}
//将最后累加的个数作为value
this.outputValue.set(sum);
//输出
context.write(key,this.outputValue);
}
}
}
区别
方式二比方式一多了一个比较器
- 方式一:实现writable接口
- 方式二:实现WritableComparable接口
多一个ComparableTo方法
- 这个比较器会在这个 类型作为Map输出的Key在shuffle中进行排序的时候会调用
- 什么时候用方式一:
这个JavaBean不会经过shuffle的排序
- 什么时候用方式二:
这个JavaBean需要作为Map输出的Key经过排序
3、总结
为什么要自定义数据类型?
因为Hadoop中数据的传输只有KV数据格式,KV只有两列,如果我们的需求中出现多列,就无法满足
- 解决:
- 要么使用Text字符串拼接
- 要么使用自定义数据类型:JavaBean
每一列作为一个属性,封装成一个JavaBean
假设有100列
50列封装成一个JavaBean,另外50列封装成另一个JavaBean
如何自定义数据类型?
两种方式
方式一:实现Writable接口
- 定义属性:列
- 重写方法
- 构造
- get and set
- 序列化:在这个属性发送时,需要调用序列化
- 反序列化:这个属性在网络中被读取时,需要调用发反序列化
保证序列化与反序列化的顺序一致即可
- toString:将这个对象转换成String类型
WordCountBean1.toString
|
word+"\t"+length
方式二:实现WritableComparable接口
- 构造
- get and set
- 序列化:在这个属性发送时,需要调用序列化
- 反序列化:这个属性在网络中被读取时,需要调用发反序列化
保证序列化与反序列化的顺序一致即可
- toString:将这个对象转换成String类型
- ComparaTo方法
功能:实现在shuffle过程中,对这个类型进行排序时,需要调用
hadoop 6 1
hadoop 4 1
hive 4 1
hbase 5 1
只有这个类型作为Map输出的Key才会被排序
shuffle中的排序只对Map输出的Key做排序
- 排序:本质就是比较
先比较第一个列,如果第一列相同,再比较第二列
hadoop 4 1
hadoop 6 1
hbase 5 1
hive 4 1
两种方式之间有什么区别?
- 方式二比方式一多一个ComparaTo方法
- 这个方法会被调用
- 这个类型会作为Map输出的key
- 这个类型会经过shuffle
- 如果你分不清,那么最简单的,只要自定义数据类型,就实现WritableComparable接口