MapReduce综合练习题(2)社交粉丝数据分析
数据准备
以下是qq的好友列表数据,冒号前是一个用户,冒号后是该用户的所有好友(数据中的好友关系是单向的)
A:B,C,D,F,E,O
B:A,C,E,K
C:F,A,D,I
D:A,E,F,L
E:B,C,D,M,L
F:A,B,C,D,E,O,M
G:A,C,D,E,F
H:A,C,D,E,O
I:A,O
J:B,O
K:A,C,D
L:D,E,F
M:E,F,G
O:A,H,I,J
把数据保存到friend.txt
逻辑分析
求出哪些人两两之间有共同好友,及他俩的共同好友都有谁?
解题思路:
第一步
map
读一行 A:B,C,D,F,E,O
输出 <B,A><C,A><D,A><F,A><E,A><O,A>
在读一行 B:A,C,E,K
输出 <A,B><C,B><E,B><K,B>
REDUCE
拿到的数据比如<C,A><C,B><C,E><C,F><C,G>…
输出:
<A-B,C>
<A-E,C>
<A-F,C>
<A-G,C>
<B-E,C>
<B-F,C>…
第二步
map
读入一行<A-B,C>
直接输出<A-B,C>
reduce
读入数据 <A-B,C><A-B,F><A-B,G>…
输出: A-B C,F,G,…
Java代码
ComonsFriendsStepOne
package com.czxy.day20191122.Test01;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
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;
public class ComonsFriendsStepOne extends Configured implements Tool {
@Override
public int run(String[] args) throws Exception {
Configuration conf = super.getConf();
Job job = Job.getInstance(conf, ComonsFriendsStepOne.class.getSimpleName());
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job,new Path("E:\\cache\\mapReduceTestCache\\20191122"));
job.setMapperClass(ComonsFriendsStepOneMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
job.setReducerClass(ComonsFriendsStepOneReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
job.setOutputFormatClass(TextOutputFormat.class);
TextOutputFormat.setOutputPath(job,new Path("E:\\cache\\mapReduceResultCache\\20191122\\Test01\\one01"));
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static class ComonsFriendsStepOneMapper extends Mapper<LongWritable,Text,Text,Text> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] split = value.toString().split(":");
//左边是用户 右边是好友列表
String person = split[0];
String[] friends = split[1].split(",");
for (String friend : friends) {
//输出好友,用户
context.write(new Text(friend),new Text(person));
}
}
}
public static class ComonsFriendsStepOneReducer extends Reducer<Text,Text,Text,Text> {
@Override
protected void reduce(Text friend, Iterable<Text> persons, Context context) throws IOException, InterruptedException {
//构建字符串,这个字符串的意思是把一个用户对应的好友都组成A-B-C
StringBuffer buffer = new StringBuffer();
for (Text person : persons) {
buffer.append(person).append("-");
}
//然后写出这个好友 的好友列表
context.write(friend,new Text(buffer.toString()));
}
}
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration();
ToolRunner.run(configuration,new ComonsFriendsStepOne(),args);
}
}
ComonsFriendsStepTwo
package com.czxy.day20191122.Test01;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
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;
import java.util.Arrays;
public class ComonsFriendsStepTwo extends Configured implements Tool {
@Override
public int run(String[] args) throws Exception {
Job job = Job.getInstance(super.getConf(), ComonsFriendsStepTwo.class.getSimpleName());
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job,new Path("E:\\cache\\mapReduceResultCache\\20191122\\Test01\\one01"));
job.setMapperClass(ComonsFriendStepTwoMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
job.setReducerClass(ComonsFriendStepTwoReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
job.setOutputFormatClass(TextOutputFormat.class);
TextOutputFormat.setOutputPath(job,new Path("E:\\cache\\mapReduceResultCache\\20191122\\Test01\\one02"));
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static class ComonsFriendStepTwoMapper extends Mapper<LongWritable,Text,Text,Text> {
/**
* A F-D-O-I-H-B-K-G-C-
* B E-A-J-F-
* C K-A-B-E-F-G-H-
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] split = value.toString().split("\t");
String friends = split[0];
String[] persons = split[1].split("-");
System.out.println("排序前");
System.out.println(Arrays.toString(persons));
//排序,避免c-b 与b-c 这样的情况出现
Arrays.sort(persons);
System.out.println("排序后:");
System.out.println(Arrays.toString(persons));
for(int i =0;i< persons.length -1 ;i++){
for(int j = i+1;j<persons.length;j++){
context.write(new Text(persons[i]+"-"+persons[j]),new Text(friends));
}
}
}
}
public static class ComonsFriendStepTwoReducer extends Reducer<Text,Text,Text,Text> {
/**
*
* A-B C
* A-B D
* A-B E
*
* @param key
* @param values
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
StringBuffer buffer = new StringBuffer();
for (Text value : values) {
buffer.append(value.toString()+"\t");
}
context.write(key,new Text(buffer.toString()));
}
}
public static void main(String[] args) throws Exception {
ToolRunner.run(new Configuration(),new ComonsFriendsStepTwo(),args);
}
}
过程分析
首先,你得把代码跑一遍,即使你看不懂它到底是什么意思。
首先我们可以看到输入的数据是这样的
这个过程分为两个阶段,每个阶段又有map和reduce的过程,所以看起来有些绕。
先说第一个过程。
1、map拿到了每个用户的好友列表。
2、map输出了它的其中一个好友,和这个用户本身。
我们知道,map输出的是 key和value 的list也就是list(key,value)这种形式。
而经过map和reduce中间的一系列过程。
它会变成一个key,list(value)这种形式被reduce接收到。
3、reduce接收到 一个作为别人好友的用户A,拥有这个用户A好友的用户列表
这时,reduce接收到:
key:好友人用户A
values:把用户A作为好友的用户们。
4、reduce接收到之后就把它们格式化了一下直接就保存到结果中了。
整个过程数据是这样变化的:
map接收:
A:B,C,D,E
map输出:
B-A
C-A
D-A
E-A
reduce接收:
B ,list(A,C,D,E)
reduce输出
B A-C-D-E
也就是说,经过第一阶段,拿到了像下面这样的数据。
然后是第二阶段。
可以看出,第二阶段接收的正是第一阶段输出的内容。
而第二阶段关键的地方来了,请看如下代码:
String[] split = value.toString().split("\t");
String friends = split[0];
String[] persons = split[1].split("-");
System.out.println("排序前");
System.out.println(Arrays.toString(persons));
//排序,避免c-b 与b-c 这样的情况出现
Arrays.sort(persons);
System.out.println("排序后:");
System.out.println(Arrays.toString(persons));
for(int i =0;i< persons.length -1 ;i++){
for(int j = i+1;j<persons.length;j++){
context.write(new Text(persons[i]+"-"+persons[j]),new Text(friends));
}
}
这里边把右边用-连起来的一串,拆分成一个数组了,这个时候给数组排序。
排序前
[J, I, H, A, F]
排序后:
[A, F, H, I, J]
以上排序结果后,再遍历两次这个数组,这个遍历实现了如下效果:
把persons数组中的第n个和它后面所有的都组成了一个A-B形式的这样的组合,然后就输出给reduce了。
这里的A-B的含义是什么呢?
注意,map接收到的数据是:
一个作为别人好友的用户A,拥有这个用户A好友的用户列表(用户们)
这时候他把有用户A好友的这些人,都配了个对,两两配对,作为key输出了
而这个好友人(工具人)A
这个A作为value。
也就是:
map输出了:
key : 好友列表有A的用户B,好友列表有A的用户C
value: 作为好友人(工具人)A
这个map输出的意思是啥?不就是
B和C有共同的好友A吗?
嗯?你说是不是?答案出来了!
所以第二阶段这个reduce接收到了这个正确答案,只是格式化了一下,就给输出了
StringBuffer buffer = new StringBuffer();
for (Text value : values) {
buffer.append(value.toString()+"\t");
}
context.write(key,new Text(buffer.toString()));
reduce 输出的正是 用户B和用户C共同拥有的好友们啊!
然后结果出来了,如下。