MapReduce实现等值连接,左外连接,右外连接,全外连接

#测试数据:

# more user.txt(用户ID,用户名)

1	lavimer
2	liaozhongmin
3	liaozemin
#more post.txt(用户ID,帖子ID,标题)

1	1	java
1	2	c
2	3	hadoop
4	4	hive
5	5	hbase
5	6	pig
5	7	flume

#等值连接结果如下:

1	lavimer	1	1	java
1	lavimer	1	2	c
2	liaozhongmin	2	3	hadoop

#左外连接结果如下:

1	lavimer	1	1	java
1	lavimer	1	2	c
2	liaozhongmin	2	3	hadoop
3	liaozemin	NULL

#右外连接结果如下:

1	lavimer	1	1	java
1	lavimer	1	2	c
2	liaozhongmin	2	3	hadoop
NULL	4	4	hive
NULL	5	5	hbase
NULL	5	6	pig
NULL	5	7	flume

#全外连接结果如下:

1	lavimer	1	1	java
1	lavimer	1	2	c
2	liaozhongmin	2	3	hadoop
3	liaozemin	NULL
NULL	4	4	hive
NULL	5	5	hbase
NULL	5	6	pig
NULL	5	7	flume

实现代码如下:

/**
 * 
 * @author 廖钟民
 * time : 2015年1月30日下午1:23:36
 * @version
 */
public class UserPostJoin {
	// 定义输入路径
	private static final String INPUT_PATH1 = "hdfs://liaozhongmin:9000/user_post_join/user.txt";
	private static final String INPUT_PATH2 = "hdfs://liaozhongmin:9000/user_post_join/post.txt";
	// 定义输出路径
	private static final String OUT_PATH = "hdfs://liaozhongmin:9000/out";

	public static void main(String[] args) {

		try {
			// 创建配置信息
			Configuration conf = new Configuration();

			// 创建文件系统
			FileSystem fileSystem = FileSystem.get(new URI(OUT_PATH), conf);
			// 如果输出目录存在,我们就删除
			if (fileSystem.exists(new Path(OUT_PATH))) {
				fileSystem.delete(new Path(OUT_PATH), true);
			}

			// 创建任务
			Job job = new Job(conf, UserPostJoin.class.getName());

			// 设置连接类型
			job.getConfiguration().set("joinType", "allOuter");
			// 设置多路径输入
			MultipleInputs.addInputPath(job, new Path(INPUT_PATH1), TextInputFormat.class, UserMapper.class);
			MultipleInputs.addInputPath(job, new Path(INPUT_PATH2), TextInputFormat.class, PostMapper.class);

			//1.2 设置自定义Mapper类和设置map函数输出数据的key和value的类型
			job.setMapOutputKeyClass(Text.class);
			job.setMapOutputValueClass(UserPost.class);

			//1.3 设置分区和reduce数量(reduce的数量,和分区的数量对应,因为分区为一个,所以reduce的数量也是一个)
			job.setPartitionerClass(HashPartitioner.class);
			job.setNumReduceTasks(1);

			//1.4 排序
			//1.5 归约
			//2.1 Shuffle把数据从Map端拷贝到Reduce端。
			//2.2 指定Reducer类和输出key和value的类型
			job.setReducerClass(UserPostReducer.class);
			job.setOutputKeyClass(Text.class);
			job.setOutputValueClass(Text.class);

			//2.3 指定输出的路径和设置输出的格式化类
			FileOutputFormat.setOutputPath(job, new Path(OUT_PATH));
			job.setOutputFormatClass(TextOutputFormat.class);

			// 提交作业 退出
			System.exit(job.waitForCompletion(true) ? 0 : 1);

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 自定义Mapper类用于处理来自user.txt文件的数据
	 * @author 廖钟民
	 * time : 2015年1月30日下午1:22:12
	 * @version
	 */
	public static class UserMapper extends Mapper<LongWritable, Text, Text, UserPost> {
		@Override
		protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, UserPost>.Context context) throws IOException, InterruptedException {
			// 对字符串进行切分
			String[] arr = value.toString().split("\t");
			// 创建UserId
			Text userId = new Text(arr[0]);
			// 把结果写出去
			context.write(userId, new UserPost("U", value.toString()));
		}
	}
	/**
	 * 自定义Mapper类用于处理来自post.txt文件的数据
	 * @author 廖钟民
	 * time : 2015年1月30日下午1:22:16
	 * @version
	 */
	public static class PostMapper extends Mapper<LongWritable, Text, Text, UserPost> {
		@Override
		protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, UserPost>.Context context) throws IOException, InterruptedException {
			// 对数据进行切分
			String[] arr = value.toString().split("\t");
			// 创建用户ID
			Text userId = new Text(arr[0]);
			context.write(userId, new UserPost("P", value.toString()));

		}
	}
	/**
	 * 自定义Reducer类用于处理不同Mapper类的输出
	 * @author 廖钟民
	 * time : 2015年1月30日下午1:23:05
	 * @version
	 */
	public static class UserPostReducer extends Reducer<Text, UserPost, Text, Text> {
		// 定义List集合用于存放用户
		private List<Text> users = new ArrayList<Text>();
		private List<Text> posts = new ArrayList<Text>();

		// 定义连接类型
		private String joinType;

		@Override
		protected void setup(Reducer<Text, UserPost, Text, Text>.Context context) throws IOException, InterruptedException {

			this.joinType = context.getConfiguration().get("joinType");

			System.out.println(joinType);

		}
		/**
		 * 经过Shuffle后数据会分组,每一组数据都会调用一次reduce()函数
		 *第一组数据:
		 *1	lavimer
		 *1	1	java
		 *1	2	c
		 *
		 *第二组数据:
		 *2	3	hadoop
		 *2	liaozhongmin
		 *
		 *第三组数据:
		 *3	liaozemin
		 *
		 *第四组数据:
		 *4	4	hive
		 *
		 *第五组数据:
		 *5	5	hbase
		 *5	6	pig
		 *5	7	flume
		 *
		 *每一组数据都会调用一次reduce()函数,我们以第一组数据为例进行讲解:
		 *
		 *进入reduce函数后,<1,lavimer>会被添加到users集合中 
		 *<1	1	java>和<1	2	c>会被添加到posts集合中
		 *
		 *然后是判断当前操作是什么类型的连接,我们以等值连接为例:
		 *遍历两个集合得到的数据为:
		 *【1	lavimer	   1         1    java】
		 *【1	lavimer	   1		 2    c】
		 *
		 *这是第一组数据的执行轨迹,其他依次类推就可以得到相关的操作
		 */
		protected void reduce(Text key, Iterable<UserPost> values, Reducer<Text, UserPost, Text, Text>.Context context) throws IOException,
				InterruptedException {
			// 清空集合
			users.clear();
			posts.clear();

			// 迭代values集合把当前穿进来的某个组中的数据分别添加到对应的集合中
			for (UserPost val : values) {
				System.out.println("实际值:" + key + "===>" + values.toString());
				if (val.getType().equals("U")) {
					users.add(new Text(val.getData()));
				} else {
					posts.add(new Text(val.getData()));
				}
			}

			// 根据joinType关键字做对应的连接操作
			if (joinType.equals("innerJoin")) {// 内连接
				if (users.size() > 0 && posts.size() > 0) {

					for (Text user : users) {
						for (Text post : posts) {
							context.write(new Text(user), new Text(post));
						}
					}
				}
			} else if (joinType.equals("leftOuter")) {// 左外连接

				for (Text user : users) {
					if (posts.size() > 0) {
						for (Text post : posts) {
							context.write(new Text(user), new Text(post));
						}
					} else {
						context.write(new Text(user), createEmptyPost());
					}
				}

			} else if (joinType.equals("rightOuter")) {// 右外连接
				for (Text post : posts) {
					if (users.size() > 0) {
						for (Text user : users) {
							context.write(new Text(user), new Text(post));
						}
					} else {
						context.write(createEmptyUser(), post);
					}
				}
			} else if (joinType.equals("allOuter")) {// 全外连接
				if (users.size() > 0) {
					for (Text user : users) {
						if (posts.size() > 0) {
							for (Text post : posts) {
								context.write(new Text(user), new Text(post));
							}
						} else {
							context.write(new Text(user), createEmptyPost());
						}
					}
				} else {
					for (Text post : posts) {
						if (users.size() > 0) {
							for (Text user : users) {
								context.write(new Text(user), new Text(post));
							}
						} else {
							context.write(createEmptyUser(), post);
						}
					}
				}
			}

		}

		/**
		 * 用户为空时用制表符代替
		 * 
		 * @return
		 */
		private Text createEmptyUser() {
			return new Text("NULL");
		}

		/**
		 * 帖子为空时用制表符代替
		 * 
		 * @return
		 */
		private Text createEmptyPost() {
			return new Text("NULL");
		}
	}
}
/**
 * 自定义实体类封装两个表的数据
 * @author 廖钟民
 * time : 2015年1月30日下午1:23:50
 * @version
 */
class UserPost implements Writable {

	// 类型(U表示用户,P表示帖子)
	private String type;
	private String data;

	public UserPost() {
	}

	public UserPost(String type, String data) {
		this.type = type;
		this.data = data;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public String getData() {
		return data;
	}

	public void setData(String data) {
		this.data = data;
	}

	public void write(DataOutput out) throws IOException {
		out.writeUTF(this.type);
		out.writeUTF(this.data);

	}

	public void readFields(DataInput in) throws IOException {
		this.type = in.readUTF();
		this.data = in.readUTF();
	}

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值