MapReduce端Join操作(Map端join、Reduce端join)

map端join
指有两种表,只是一张较小,一张较大(一般大于1万条数据),大表的信息完全可以覆盖小表,往往将较小的表以键值对的形式添加到内存中,,然后只扫描大表:对于大表中的每一条记录key/value,在小表中查找是否有相同的key的记录,如果有,则连接后输出即可。
Map端join是数据到达map处理函数之前进行合并的,效率要远远高于Reduce端join,因为Reduce端join是把所有的数据都经过Shuffle,非常消耗资源。所以一般都用Map端join。
操作流程
1.以键值对形式存储小表信息;
2.在Mapper.setup(Context context)函数里读取该文件并存到Map中;
3.读大表信息,根据外键关联,大表的第二列就是小表的第一列,所以键相同,然后通过大表的第二列的键获取小表中的值,将得到的值将填充回大表中的第二列;
4.将最后得到的值转为字符串;
5.将结果输出(即没有Reduce任务)。
上代码:

public class MyMapJoin {
    public static class MyMapper extends Mapper<LongWritable, Text,Text, NullWritable>{
        private Map myType = new HashMap();//存储小表信息
            @Override
            protected void setup(Context context) throws IOException, InterruptedException {
              	//获取缓存中的小表,返回的是小表的URI
                String fileName = context.getCacheFiles()[0].getPath();
           	final BufferedReader read = new BufferedReader(new FileReader(fileName));//buffer读数据增加效率
            	String str = null;
            	while ((str=read.readLine())!=null){
                String [] sps = str.split(",");//通过逗号进行分割
                myType.put(sps[0],sps[1]);//分割后第一列放到键,第二列放到值
            	}	
            	read.close();
       	    }
       	    @Override
       	    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException 
           //读大表
           String [] goodInfos = value.toString().split(",");
           //根据主外键获取大表分类,外键就是第2列,根据外键获取小表第一列的值
           String type = myType.get(goodInfos[1]).toString();
           //将数据填充回大表数组中的第二列
           goodInfos[1] =type;
           //将数组转为字符串
           Text txt = new Text(Arrays.toString(goodInfos));
           context.write(txt,NullWritable.get());
       }
   }
    public static class MyReduce extends Reducer<Text,NullWritable,Text, NullWritable> {

    }
    //主启动类
    public class Driver{

    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException, URISyntaxException {
	Configuration conf = new Configuration();
        //准备一个空任务
        Job job = Job.getInstance(conf);
        //设置该任务的主启动类
        job.setJarByClass(Driver.class);
        //设置任务的输入数据源文件,也就是大表的文件夹
        FileInputFormat.addInputPath(job,new Path("e://xxx"));
        //设置你的mapper任务类
        job.setMapperClass(MyMapper.class);
        //设置mapper任务类的输出数据类型 key和value类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(NullWritable.class);
        //放到linux系统下读hdfs文件的路径
        //job.addCacheFile(new URI("hdfs://192.168.xx.xxx:9000/mydemo/xxx.csv"));

        //读本地文件,也就是小表
        job.addFileToClassPath(new Path("e://xxx.csv"));
        //设置任务的输出数据源文件
        FileOutputFormat.setOutputPath(job,new Path("e://gg"));
        //启动任务并执行
        job.waitForCompletion(true);

    }
}
}

reduce端join
两张都是大表,所以同一个key对应的字段可能位于不同map中,把两张表放在同一目录下,map函数同时读取两个文件File1和File2,为了区分两种来源的key/value数据对,对每条数据打一个标签,File1就用 type: File2就用context: 根据文件名进行判断File1 和File2,由于有外键关联,输出的时候把相同的外键作为键输出。
在reduce中按分类编号已经分好组 ,处理两种不同的数据先将迭代器中的数据存放到集合中;在数组中找到前面有type:的信息,通过截取得到值;将其它的信息遍历替换中间标签的内容,最后输出。
操作流程
1.两张表放在同一目录下,map函数同时读取两个文件;
2.((FileSplit)context.getInputSplit()).getPath().toString()获取当前行数据出自哪个文件的文件名;
3.path.contains(“type”)判断是哪个文件,把相同的外键作为键输出;
4.增强型for遍历迭代器中的数据放到集合中;
5.通过.substring截取有type:的后面的值;
6.将其它的信息遍历,替换context:的内容变为刚才截取有type:的后面的值;
7.在主启动类输出。
代码

public class MyReduceInner {
    public static class MyMapper extends Mapper<LongWritable, Text,Text,Text>{

        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String [] words = value.toString().split(",");
            //获取当前行数据出自哪个文件的文件名
            String path = ((FileSplit)context.getInputSplit()).getPath().toString();
            //不同文件输出的键的位置不同
            if(path.contains("type")){//两个文件有外键关联,找到外键作为相同的键,放到一组
                //.contains返回一个值,该值指示指定的 String 对象是否出现在此字符串中。
                context.write(new Text(words[0]),new Text("type:"+words[1]));//找到文件关联的键的列是第一列
            }else {
                context.write(new Text(words[1]),new Text("context:"+words[0]+":"+words[1]+":"+words[2]));//找到文件关联的外键的列的位置是第二个
            }
        }
    }

    public static class MyReduce extends Reducer< Text,Text,Text, NullWritable>{
        @Override
        protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
            //按分类编号已经分好组 处理两种不同的数据
            //先将迭代器中的数据存放到集合中
            //把迭代器转换为集合
            List<Text> lst = new ArrayList<Text>();
            for (Text tx : values) {
                String s = tx.toString();//先转为字符串
                lst.add(new Text(s));
            }
            //在数组中找到前面有type:的信息  用来获得值的信息
            String typeInfo = "";
            for (Text tx : lst) {
                String val = tx.toString();
                if(val.contains("type")){
                    typeInfo = val.substring(val.indexOf(":")+1);//截取冒号后面一位
                    //在集合中移除本条信息
                    lst.remove(tx);
                    break;
                }
            }
            //将其它的信息遍历替换context:的内容变为刚才截取有type:的后面的值
            for (Text tx : lst) {
                String [] infos = tx.toString().split(":");
                infos[2] = typeInfo;//拿type:截取过后的值赋给数组第二个下标,因为外键相同
                infos[0] = String.valueOf(key);//把外键替换掉标识context:
                context.write(new Text(Arrays.toString(infos)),NullWritable.get());
            }
        }
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
       
        Configuration conf = new Configuration();
        //准备一个空任务
        Job job = Job.getInstance(conf);
        //设置该任务的主启动类,本类
        job.setJarByClass(MyReduceInner.class);
        //设置任务的输入数据源文件,存2张表的目录
        FileInputFormat.addInputPath(job,new Path("e://xxx"));

        //设置你的mapper任务类
        job.setMapperClass(MyMapper.class);
        //设置mapper任务类的输出数据类型 key和value类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);

        //设置你的reduce任务类
        job.setReducerClass(MyReduce.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(NullWritable.class);
        //设置任务的输出数据源文件
        FileOutputFormat.setOutputPath(job,new Path("e://xx"));
        //启动任务并执行
        job.waitForCompletion(true);

    }
}

猜你喜欢

转载自blog.csdn.net/zp17834994071/article/details/106617699