Hadoop中join操作

1.概述

      **在传统数据库(如:MYSQL)中,JOIN操作是非常常见且非常耗时的。而在HADOOP中进行JOIN操作,同样常见且耗时,由于Hadoop的独特设计思想,当进行JOIN操作时,有一些特殊的技巧**。

reduce side join:
假设要进行join的数据分别来自File1和File2.
reduce side join是一种最简单的join方式,其主要思想如下:
在map阶段,map函数同时读取两个文件File1和File2,为了区分两种来源的key/value数据对,对每条数据打一个标签(tag),比如:tag=0表示来自文件File1,tag=2表示来自文件File2。即:map阶段的主要任务是对不同文件中的数据打标签。
在reduce阶段,reduce函数获取key相同的来自File1和File2文件的value list, 然后对于同一个key,对File1和File2中的数据进行join(笛卡尔乘积)。即:reduce阶段进行实际的连接操作。
整个计算过程是:
(1)在map阶段,把所有记录标记成<key, value>的形式,其中key是id,value则根据来源不同取不同的形式:来源于表A的记录,value的值为"a#"+name;来源于表B的记录,value的值为"b#"+score。
(2)在reduce阶段,先把每个key下的value列表拆分为分别来自表A和表B的两部分,分别放入两个向量中。然后遍历两个向量做笛卡尔积,形成一条条最终结果。

2.实现机制
通过将关联的条件pid作为map输出的key,将两表满足join条件的数据并携带数据所来源的文件信息,发往同
一个reducetask,在reduce中进行数据的串联

待测试文件hadoop828_1.csv
在这里插入图片描述
hadoop828_2.csv
在这里插入图片描述

3. 实体类

package com.root.table;

import org.apache.hadoop.io.Writable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

public class TableBean implements Writable {
    private String country;  //国家(作为唯一连接字符)
    private String firstname;   //名字
    private String lastname;   //姓氏
    private String category; // 类别
    private int survived; //存活
    private String sex;   //性别
    private int age;    //年龄
    private String flag;  //标记位,用来区分究竟是哪一个文件。

    public TableBean() {
        super();
    }

    public TableBean(String country, String firstname, String lastname, String category, int survived, String sex, int age, String flag) {
        super();
        this.country = country;
        this.firstname = firstname;
        this.lastname = lastname;
        this.category = category;
        this.survived = survived;
        this.sex = sex;
        this.age = age;
        this.flag = flag;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getFirstname() {
        return firstname;
    }

    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }

    public String getLastname() {
        return lastname;
    }

    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public int getSurvived() {
        return survived;
    }

    public void setSurvived(int survived) {
        this.survived = survived;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getFlag() {
        return flag;
    }

    public void setFlag(String flag) {
        this.flag = flag;
    }

    public void write(DataOutput dataOutput) throws IOException {

        //序列化
        dataOutput.writeUTF(country);
        dataOutput.writeUTF(firstname);
        dataOutput.writeUTF(lastname);
        dataOutput.writeUTF(sex);
        dataOutput.writeInt(age);
        dataOutput.writeUTF(category);
        dataOutput.writeInt(survived);
        dataOutput.writeUTF(flag);
    }

    public void readFields(DataInput dataInput) throws IOException {

        //反序列化
        country = dataInput.readUTF();
        firstname = dataInput.readUTF();
        lastname = dataInput.readUTF();
        sex = dataInput.readUTF();
        age = dataInput.readInt();
        category = dataInput.readUTF();
        survived = dataInput.readInt();
        flag = dataInput.readUTF();


    }

    @Override
    public String toString() {
        return country + '\t' +
                firstname + '\t' +
                lastname + '\t' +
                sex + "\t" + age + "\t" +
                category + '\t' +
                +survived + "\r\n";
    }
}

实体类的flag变量用来存储文件名(做一个标记),对于map阶段的数据打上标签。

4.Map程序
**

package com.root.table;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import java.io.IOException;
public class TableMapper extends Mapper<LongWritable, Text, Text, TableBean> {
    FileSplit inputSplit;
    String file_name;
    Text k = new Text();
    TableBean v = new TableBean();
    //重写setup方法,该方法由底层run方法调用,用来获取文件名,有点初始化的味道.
    @Override
    protected void setup(Context context) throws IOException, InterruptedException {
        // 获取文件的名称
        inputSplit = (FileSplit) context.getInputSplit();
        file_name = inputSplit.getPath().getName();
    }
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //  国家   性别  年龄   hadoop828_1.csv文件
        //Sweden	F	65
        //国家      名字  姓氏   类别 存活  hadoop828_2.csv文件
        //  Estonia	LEA	AALISTE	C	0
        //获取一行
        String line = value.toString();
        if (file_name.startsWith("hadoop828_1")) {
            String[] fields = line.split(",");
            //封装k和v
            k.set(fields[0]);
            v.setCountry(fields[0]);
            v.setSex(fields[1]);
            v.setAge(Integer.parseInt(fields[2]));
            v.setFirstname("");
            v.setLastname("");
            v.setCategory("");
            v.setSurvived(0);
            v.setFlag("hadoop828_1");
        } else {
            String[] fields = line.split(",");
            //封装k和v
            k.set(fields[0]);
            v.setCountry(fields[0]);
            v.setSex("");
            v.setAge(0);
            v.setFirstname(fields[1]);
            v.setLastname(fields[2]);
            v.setCategory(fields[3]);
            v.setSurvived(Integer.parseInt(fields[4]));
            v.setFlag("hadoop828_2");
        }
        //写出
        context.write(k,v);
    }
}

请特别留意这个setup方法,它的目的是为了文件的名称

5.Reduce程序

package com.root.table;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;

public class TableReduce extends Reducer<Text, TableBean, TableBean, NullWritable> {


    @Override
    protected void reduce(Text key, Iterable<TableBean> values, Context context) throws IOException, InterruptedException {

        //存储hadoop828_1.csv文件数据集合
        ArrayList<TableBean> atableBean = new ArrayList<TableBean>();

        //存储hadoop828_2.csv文件数据集合
        ArrayList<TableBean> btableBean = new ArrayList<TableBean>();

        for (TableBean value : values) {

            if ("hadoop828_1".equals(value.getFlag())) { //hadoop828_1.csv文件
                TableBean tmp1Bean = new TableBean();
                try {
                    BeanUtils.copyProperties(tmp1Bean, value);

                    atableBean.add(tmp1Bean);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }

            } else {    //hadoop828_2.csv文件
                TableBean tmp2Bean = new TableBean();
                try {
                    BeanUtils.copyProperties(tmp2Bean, value);
                    btableBean.add(tmp2Bean);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }

        }
        for (TableBean tableBean1 : atableBean) {
            for (TableBean tableBean2:btableBean){
                tableBean1.setFirstname(tableBean2.getFirstname());
                tableBean1.setLastname(tableBean2.getLastname());
                tableBean1.setCategory(tableBean2.getCategory());
                tableBean1.setSurvived(tableBean2.getSurvived());
                //写出
                context.write(tableBean1, NullWritable.get());
            }




        }
    }
}

**特别注意:
for (TableBean value : values) {

        if ("hadoop828_1".equals(value.getFlag())) { //hadoop828_1.csv文件
            TableBean tmp1Bean = new TableBean();
            try {
                BeanUtils.copyProperties(tmp1Bean, value);

                atableBean.add(tmp1Bean);

**

**这里不能用value作为参数传入atableBean.add()方法里,我这块也会经常被误导,TableBean value是一个引用(储存在栈内存中), TableBean tmp1Bean = new TableBean(); tmp1Bean是一个对象(储存在堆内存中),对于这个程序来说,迭代次数value指向的对象一直在变,value的指向只取最后一次迭代的结果,new TableBean();则迭代次数n等于它在堆内存中创建了n个不同的对象. 这个就很官方:
1 、栈区( stack )— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限.
2 、堆区( heap )— 亦称动态内存分配.程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存.但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表. **

package com.root.table;

import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;

import java.io.IOException;

public class TableWriter extends RecordWriter<TableBean, NullWritable> {
    FileSystem fs;
    FSDataOutputStream Estonia, Latvia, Russia,Sweden;

    public TableWriter(TaskAttemptContext taskAttemptContext) {


        try {

            //1. 获取文件系统
            fs = FileSystem.get(taskAttemptContext.getConfiguration());

            //2.创建输出到Estonia.csv的输出流
            Estonia = fs.create(new Path("F:\\scala\\Workerhdfs\\output12\\Estonia.csv"));

            //3.创建输出到Latvia.csv的输出流
            Latvia = fs.create(new Path("F:\\scala\\Workerhdfs\\output12\\Latvia.csv"));

            //4.创建输出到Russia.csv的输出流
            Russia = fs.create(new Path("F:\\scala\\Workerhdfs\\output12\\Russia.csv"));

            //5.创建输出到Sweden.csv的输出流
            Sweden = fs.create(new Path("F:\\scala\\Workerhdfs\\output12\\Sweden.csv"));
        } catch (IOException e) {
            e.printStackTrace();
        }


    }



    public void write(TableBean tableBean, NullWritable nullWritable) throws IOException, InterruptedException {
        String line = tableBean.toString();
        if (line.contains("Estonia")) {
            Estonia.write(line.getBytes());
        } else if (line.contains("Latvia")) {
            Latvia.write(line.getBytes());
        } else if (line.contains("Russia")) {
            Russia.write(line.getBytes());
        } else {
            Sweden.write(line.getBytes());
        }
    }

    public void close(TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
        IOUtils.closeStream(Estonia);
        IOUtils.closeStream(Latvia);
        IOUtils.closeStream(Russia);
        IOUtils.closeStream(Sweden);
    }
}

根据自己的电脑(操作系统)选择合适的路径

**

package com.root.table;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
public class TableDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
 args = new String[]{"F:\\scala\\Workerhdfs\\input6","F:\\scala\\Workerhdfs\\output12"};
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);
        //2设置jar存储位置
        job.setJarByClass(TableDriver.class);
        //3关联Map类与Reduce类
        job.setMapperClass(TableMapper.class);
        job.setReducerClass(TableReduce.class);
        //4.设置Mapper阶段输出数据的key和value类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(TableBean.class);
        //5.设置最终输出数据的key和value类型
        job.setOutputKeyClass(TableBean.class);
        job.setOutputValueClass(NullWritable.class);
        job.setOutputFormatClass(TableOutputFormat.class);
        //6 设置输入路径和输出路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));
        //7提交job
//        job.submit();
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);
    }
}

**
效果如下:
在这里插入图片描述

数据很乱不要急,点工具栏中的数据
在这里插入图片描述
选中第一列数据
选择分列,这里按照制表符(\t)进行分列,

在这里插入图片描述
点击下一步
在这里插入图片描述
以table作为分割符,如果程序中实体类中的toString方法不是以table作为分割,请勾选其他选项
下一步 --》完成
在这里插入图片描述
顿时好多了,其他文件亦是如此
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
将程序运行于集群上.
在这里插入图片描述
在这里插入图片描述
查看文件前五行:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/changshupx/article/details/108278786