数据类型与自定义数据类型

  • 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接口