MapReduce参数详解(项目:好友推荐)

目录

 

一、项目需求

(1)需求简介

(2)问题描述

(3)需求理解

(4)相关数据

二、实现思路(想看程序如何设计的,不看代码,看这个也行)

(1)Map设计:

(2)Reduce设计

三、代码与详解(比较精髓,但仅限于个人理解)

(1)map代码

(2)Reduce代码

(3)Bean代码

(4)Job代码,也是程序的入口

四、程序运行结果


一、项目需求

(1)需求简介

类似于QQ好友推荐,在现实生活中,如果你的好友A和B都有一个共同的好友C,那么C很可能是你的潜在好友。如果C在你的朋友的关系中出现的次数越多,这种概率越大。

(2)问题描述

给出以下数据,为每个人推荐好友,按照共同好友的多少排序(最多10个),数据代表3有后面的好友,也代表,后面的好友都有3这个好友,因此后面好友都算是互相的隐藏好友。

(3)需求理解

社交网站上的各个用户以及用户之间的相互关注可以抽象为一个图。以下图为例:

顶点A、B、C到I分别是社交网站的用户,两顶点之间的边表示两顶点代表的用户之间相互关注。那么如何根据用户之间相互关注所构成的图,来向每个用户推荐好友呢?

现在我们以上图为例,介绍下如何利用用户之间相互关注所构成的图,来向每个用户推荐好友。首先我们不得不假设的是如果两用户之间相互关注,那么我们认为他们认识或者说是现实中的好友,至少应该认识。假设我们现在需要向用户I推荐好友,我们发现用户I的好友有H、G、C。其中H的好友还有A,G的好友还有F,C的好友还有B、F。那么用户I、H、G、C、A、B、F极有可能是同一个圈子里的人。我们应该把用户A、B、F推荐给用户I认识。进一步的想,用户F跟两位I的好友C、G是好友,而用户A、B都分别只跟一位I的好友是好友,那么相对于A、B来说,F当然更应该推荐给用户I认识。

(4)相关数据

可自行设计,可以使用如下数据进行操作。

链接:https://pan.baidu.com/s/1YWwhdfviRt7Cncjy9YXZvg 
提取码:p8i7 

如何将数据导入,参考上一篇文章https://blog.csdn.net/qq_40304825/article/details/90900005

二、实现思路

(1)Map设计:

  1. 从单行数据(0 1,3,4,5,7)入手。
  2. 得出0和1是直接好友,1和3是间接好友。
  3. 依照直接或者间接关系,打上标签,true为直接好友,false为间接好友
    • 如0号和1号是直接好友,我们建立键值对,打上标签为(0 : 1,true)
    • 如1号和3号暂时是间接好友,我们建立键值对,打上标签为(1 : 3,false)

以此类推:

  • 直接好友:首先对用户0号本身,将0与其好友打上标签。
    结果为:(0: true,1)    (0: true,3)    (0: true,4)    (0: true,5)    (0: true,7)
  • 间接好友:然后对于好友集合:笛卡尔乘积,互相打上标签2。1,3,4,5,7 X 1,3,4,5,7。(1: false,3)    (3: false,1)    (1: false,4)    (4: false,1)    等…….

然后对于好友集合:笛卡尔乘积

(2)Reduce设计

Reduce接收分三种情况:

  • 第一种情况:(0,true,1),(0,true,3),(0,true,4)
    对于这情况(标签为true)-直接好友
    如:(0: true,1) 告诉了我们0用户和1用户肯定是直接好友,所以我们不用把1用户推荐给0用户。
  • 第二种情况(标签为2) 
    说明是暂时的间接好友
    如:(1: false,4) 告诉了我们1用户和4用户暂时是间接好友关系,可以考虑进行推荐,我们的reduce过程便可以进行记录
  • 第三种情况:如果又来了一个(1: false,4)呢?这说明了什么?
    说明14有两个共同好友
  • 第四种情况:如果之后又出现(1: true,4)呢?
    这说明1和4他们已经互相认识了,我们不再将4推荐给1了

 

实现:接收map传递的键值对,要求key为0 如 (0: false,3)

  1. 创建一个容器进行记录(用HashMap)(用户id:共同好友数),HashSet表(该用户的直接好友)
  2. 如果标签为true,则添加至直接好友中,
  3. 如果标签为2并且没在黑名单,便进行记录计数
  4. 如 map.put(3,1)表示3与之有一个共同好友,再来一个就是map.put(3,1+1)

三、代码与详解

如何创建工程也在上一篇博客上写到https://blog.csdn.net/qq_40304825/article/details/90900005

(1)map代码

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;//注意此处Mapper的引用

import java.io.IOException;


public class FriendCountMapper extends Mapper<LongWritable, Text,Text, FriendCountBean> {
    /*注意Mapper的四个范型,分别代表了输入的键值对类型,输出的键值对类型,一共四个。
    *其中LongWritable是apache自定的基本数据类型,相当于平时我们理解的Long。
    *其中Text是apache自定的基本数据类型,相当于平时我们理解的String,但是在操作时候最好调用toString方法,再进行操作。
    *第一个范型(LongWritable)代表了本行数据是第几行,因为map是按行进行运行的。
    *第二个范型(Text)代表了该行的文本信息。
    *第三个范型(Text)代表了输出key的类型,这样结合map的Context来看。
    *第四个范型(FriendCountBean)首先,这是我们自定义的类,其次,这个代表map输出value的类型。
    **/
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        /*
        *该函数有三个参数类型,分别代表了如下信息:
        *第一个参数,对应上面的第一个范型,意义也相同
        *第二个参数,对应上面的第二个范型,意义相同
        *第三个参数,表示该Map最终的输出,在本程序的末尾我们可以看到我们输出的是一个键值对,其键值对的形式为<Text,FriendCountBean>,与上面最后两个参数相对应。
        **/
        String data = value.toString();    //将Text类型转换成我们熟悉的String,再操作
        String[] userAndFriends = data.split("\t");    //获得用户和该用户的朋友们
        if (userAndFriends.length != 2){    
            System.err.println("该行不成功");
            return;                        //返回为空,就代表这个Map废弃掉了
        }
        //接下来的操作就根据实现思路进行设计
        String user = userAndFriends[0];
        String[] friends = userAndFriends[1].split(",");
        for (String friend:friends) {
            context.write(new Text(user), new FriendCountBean(friend, true));
        }
        for (int i = 0;i < friends.length; i++){
            for (int j = i+1 ;j < friends.length; j++){
                /*一个Map可多次调用context.write,即一个Map可写入多行数据给Reduce进行操作,Reduce也是按行操作的。*/
                context.write(new Text(friends[i]), new FriendCountBean(friends[j], false));
                context.write(new Text(friends[j]), new FriendCountBean(friends[i], false));
            }
        }
    }
}

(2)Reduce代码

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;
import java.util.*;

public class FriendCountReducer extends Reducer<Text, FriendCountBean, Text, Text> {
    /*与Map相同,四个参数都分别代表输入的键值对和输出的键值对。
    *但是有一点需要注意的是,Reduce的前两个就是Map的后两个,这个也很容易理解,因为Reduce的输入是Map通过调用Context.write方法产生的。
    **/
    @Override
    protected void reduce(Text key, Iterable<FriendCountBean> values, Context context) throws IOException, InterruptedException {
        //与Map的参数类型相似,可同步理解
        /*需注意,这里key不再是行数,而是Map传递过来的key
        *values是一个迭代器,因为多个Map可能对同一个key进行了操作,因此他们将操作得到的结果放在一起,共享相同的key,因此,我们操作时,需要使用迭代的方法取出相应的值
        **/
        

        //接下来就按设计思路去进行代码编写
        Map<String,Integer> recommendation = new HashMap<String, Integer>();    //用于记录这个朋友被几个人推荐过
        Set<String> friendsList = new HashSet<String>();    //用于记录该用户(key)的好友列表
        //统计出每个人的推荐度,格式为<用户名:推荐度>
        for (FriendCountBean friend:values) {//遍历多个值
            String name = friend.getName();
            if (!recommendation.containsKey(friend.getName())){//如果不存在这个朋友的信息,则添加
                recommendation.put(name,0);
            }
            if (friend.isFriend()){//如果是朋友,就把之前的推荐度清空,并加入朋友列表中
                friendsList.add(name);
                recommendation.put(name,0);
                continue;
            }else if (!friendsList.contains(name)){//如果不存在朋友列表中,就添加推荐度
                int count = recommendation.get(name);
                count++;
                recommendation.put(name, count);
            }
        }
        //根据推荐度对其进行排序
        List<Map.Entry<String,Integer>> list = new ArrayList<Map.Entry<String,Integer>>(recommendation.entrySet());
        Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
            public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                return o2.getValue()-o1.getValue();
            }
        });
        String message = "";
        //对已进行排序的列表选取前十个打印
        //如果推荐度为0,就从此以后的数据不打印
        if (list.size() >= 10){
            for(int i = 0;i<10;i++){
                if (list.get(i).getValue() <= 0){
                    break;
                }
                message+="推荐:"+list.get(i).getKey()+",推荐度:"+list.get(i).getValue()+"; ";
            }
        }else {//如果不够10个元素,就遍历
            for(int i = 0; i<list.size();i++){
                if (list.get(i).getValue() <= 0){
                    break;
                }
                message+="推荐:"+list.get(i).getKey()+",推荐度:"+list.get(i).getValue()+"; ";
            }
        }
        context.write(key, new Text(message));
    }
}

(3)Bean代码

import org.apache.hadoop.io.Writable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/*
*这个类,主要存放用户名和是否是直接好友关系
*/
public class FriendCountBean implements Writable {//必须实现这个接口,并实现相应的方法
    //更严谨一点的话,这里应该是private
    String name;
    boolean isFriend;

    //构造函数一定要写一个无参构造函数,不然会报错
    public FriendCountBean(String name, boolean isFriend) {
        this.name = name;
        this.isFriend = isFriend;
    }
    
    public FriendCountBean() {
    }

    public String getName() {
        return name;
    }

    public boolean isFriend() {
        return isFriend;
    }

    //必须要实现的两个方法,基本也是格式化的,就这么写就行了
    public void write(DataOutput dataOutput) throws IOException {
        dataOutput.writeUTF(name);
        dataOutput.writeBoolean(isFriend);
    }

    public void readFields(DataInput dataInput) throws IOException {
        this.name = dataInput.readUTF();
        this.isFriend = dataInput.readBoolean();
    }
}

(4)Job代码,也是程序的入口

import org.apache.hadoop.conf.Configuration;
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.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;


//这个也是固定了的,只要把相关的参数修改成自己的就行
public class FriendCount {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Configuration configuration = new Configuration();

        Job job = Job.getInstance(configuration, "friend count");

        job.setMapperClass(FriendCountMapper.class);
        job.setReducerClass(FriendCountReducer.class);


        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(FriendCountBean.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);

        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        boolean result = job.waitForCompletion(true);
        if (!result){
            System.out.println("friend count failed");
        }
    }
}

 

四、程序运行结果

如何运行程序,在上一篇文章中有做过相应的解释https://blog.csdn.net/qq_40304825/article/details/90900005

数据有点多,看部分结果

 

 

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值