使用Hadoop寻找两两用户之间有共同好友但他们不是好友的用户
1.问题描述
假定有一个社会网络A,每一个顶点表示一位用户,每一条边表示连接的两位用户是好友。A=(V,G),其中V是用户的集合,G是边的集合,即好友的集合。用文件分别保存V和G的数据,示例如下。例如G文件中,“a,b”表示用户a和用户b是好友。
#V文件:
a
b
c
…
#G文件:
a,b
a,c
b,e
b,k
…
找出哪些用户两两之间有共同好友,但他俩不是好友,输出这些用户对的列表。例如:
b-c
……
2.思路
这个问题跟问题2有一些相似之处,不过该问题只是输出哪些用户两两之间有共同好友,但他俩不是好友。该问题的Map端代码可以与问题2一样,只需要在Reduce端以及main( )函数修改一下就可以了。
main( )函数只需要将G文件(好友集合)放到distributed cache file中,Reduce端重写的setup( )函数读取按行该缓存文件生成一个个字符串并将其添加到一个ArrayList链表中。重写的reduce( )函数处理经过了Map端和Reduce端的Shuffle过程处理生成的数据<key, value-list>。具体是判断之前的ArrayList链表中是否包含该key。如果不包含,就将值设为空,将key写到最终文件里中;包含则不做处理。
3.带注解的MapReduce程序
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import org.apache.hadoop.conf.Configuration;
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.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class Friends {
//Map处理逻辑具体代码
public static class FriMap extends Mapper<Object, Text, Text, Text> {
@Override
protected void map(Object key, Text value, Mapper<Object, Text, Text, Text>.Context context)
throws IOException, InterruptedException {
//将读取的一行内容转为字符串
String line = value.toString();
//通过“: ”对字符串切割得到该用户
String commonFriend = line.split(": ")[0];
//通过“: ”对字符串切割得到该用户的好友列表之后再对好友列表切割得到该用户的好友数组
String[] friendsArray = line.split(": ")[1].split(",");
for (int i = 0; i < friendsArray.length - 1; i++) {
for (int j = i + 1; j < friendsArray.length; j++) {
//通过双重for循环按顺序取出该用户的所有两个好友组合
String first = friendsArray[i];
String second = friendsArray[j];
String combineFriends = first + "-" + second;
context.write(new Text(combineFriends), new Text(commonFriend));
}
}
}
}
//Reduce处理逻辑具体代码
public static class FriReduce extends Reducer<Text, Text, Text, NullWritable> {
//创建一个ArrayList来保存G文件(好友集合)里面的好友关系
ArrayList<String> removeFriends = new ArrayList<>();
@Override
protected void setup(Reducer<Text, Text, Text, NullWritable>.Context context) throws IOException, InterruptedException {
super.setup(context);
try {
//将distributed cache file装入各个Map节点本地的内存数据joinData中
Path [] cacheFiles = context.getLocalCacheFiles();
if (cacheFiles != null && cacheFiles.length > 0) {
String line;
//用来保存第一个用户
String friendOne = " ";
//用来保存第二个用户
String friendTwo = " ";
BufferedReader joinReader = new BufferedReader(new FileReader(cacheFiles[0].getName()));
try {
while ((line = joinReader.readLine()) != null) {
//对读取的数据行处理
friendOne = line.split(",")[0];
friendTwo = line.split(",")[1];
//将这两个好友进行排序
if(friendOne.charAt(0) > friendTwo.charAt(0)) {
String temp = friendOne;
friendOne = friendTwo;
friendTwo = temp;
}
//两个用户拼接后添加进ArrayList集合中
removeFriends.add(friendOne + "-" + friendTwo);
}
} finally {
joinReader.close();
}
}
} catch (IOException e) {
System.err.println("Exception reading DistributedCache: " + e);
}
}
@Override
protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, NullWritable>.Context context)
throws IOException, InterruptedException {
/*
* 判断好友集合中是否包含这两个用户
* 如果不包含,则将value设置为空,将这两个用户写入文件中
* 否则不做处理
*/
if (!removeFriends.contains(key.toString())) {
//context.write(key, new Text(""));
context.write(key, NullWritable.get());
}
}
}
//main()方法
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException, URISyntaxException {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://localhost:9000");
String[] str = new String[] {"friends_output1/part-r-00000", "friends_output3"};
String[] otherArgs = (new GenericOptionsParser(conf, str)).getRemainingArgs();
if (otherArgs.length != 2) {
System.err.println("Usage: Friends and duplicate removal <in> <out>");
System.exit(2);
}
Job job = Job.getInstance(conf, "Friends_03"); //设置环境参数
job.setJarByClass(Friends.class); //设置整个程序的类名
job.setMapperClass(Friends.FriMap.class); //添加Mapper类
job.setReducerClass(Friends.FriReduce.class); //添加Reducer类
job.setOutputKeyClass(Text.class); //设置输出键的类型
job.setOutputValueClass(Text.class); //设置输出值的类型
//将G文件(好友集合)放入distributed cache file中
job.addCacheFile(new URI("friends_input/G.txt"));
//设置输入目录
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
Path outputPath = new Path(otherArgs[1]);
//输出目录已经存在就删除输出目录
if(outputPath.getFileSystem(conf).exists(outputPath))
outputPath.getFileSystem(conf).delete(outputPath, true);
//设置输出目录
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
System.exit(job.waitForCompletion(true) ? 0:1);
}
}
用到的文件
问题1的输出文件(part-r-00000)
a: b,c,e,g,k
b: a,e,k
c: a,d,g
d: c
e: a,b,g,k
f: g,i
g: a,c,e,f,i,k
h: j,k
i: f,g,j
j: h,i,k
k: a,b,e,g,h,j
输入文件位置:
friends_output1/part-r-00000
输出文件目录:
friends_output3