• 使用hadoop解决找博客共同好友问题


    写在前面

    首先我们来看看需求,以下是某博客的好友列表数据,冒号前是一个用户,冒号后是该用户的所有好友(好友关系是单向的):

    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
    

    代码

    Mapper

    public class FriendsMapper extends Mapper<LongWritable, Text, Text, Text> {
        Text k = new Text();
        Text v = new Text();
    
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            // A:B,C,D,F,E,O
            String line = value.toString();
    
            // 切割数据 变成: key:A value:B,C,D,F,E,O
            String[] fields = line.split(":");
            // 封装k,v 写出数据
            if (ArrayUtils.isNotEmpty(fields)) {
                k.set(fields[0]);
                v.set(fields[1]);
            }
    
            context.write(k, v);
    
        }
    }
    

    Reducer

    public class FriendsReducer extends Reducer<Text, Text, Text, NullWritable> {
        private final Map<String, String> oldMap = new HashMap<>();
        private final Text k = new Text();
    
        @Override
        protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
            // 将key与value封装成map
            for (Text value : values) {
                oldMap.put(key.toString(), value.toString());
            }
        }
    
        /**
         * cleanup方法发生在所有reduce执行完之后,故这时候的oldMap已经填充了所有人的数据
         * @param context
         * @throws IOException
         * @throws InterruptedException
         */
        @Override
        protected void cleanup(Context context) throws IOException, InterruptedException {
            // key:A value:B,C,D,F,E,O
            // 使用一个list存储所有人的相同好友
            List<String> sameFriends = new ArrayList<>();
            // 获取所有用户集合的key
            Set<String> keySet = oldMap.keySet();
    
            // 开始迭代
            for (String keyOne:keySet) {
    
                for (String keyTwo:keySet) {
                    // 获取第一个人的好友
                    List<String> firstCommonList = getCommonList(keyOne);
    
                    // 获取第二个人的好友
                    List<String> secondCommonList = getCommonList(keyTwo);
    
                    // 求二者的交集,存储到firstCommonList
                    firstCommonList.retainAll(secondCommonList);
    
                    // 拼接要输出的字符串
                    StringBuilder sb = new StringBuilder();
                    for (String s : firstCommonList) {
                        sb.append(s).append(" ");
                    }
    
                    // 筛选掉没有共同好友的人,即sb没有拼接,为空串
                    if (!StringUtils.equals("",sb.toString())) {
                        sameFriends.add(keyOne + "和" + keyTwo + "的共同好友为:" + sb.toString());
                    }
                }
            }
    
            // 拼接完毕,输出共同好友
            for (String sameFriend : sameFriends) {
                k.set(sameFriend);
                context.write(k, NullWritable.get());
            }
        }
    
        private List<String> getCommonList(String key) {
            String friend = oldMap.get(key);
            String[] common = friend.split(",");
            List<String> commonListTmp = Arrays.asList(common);
            return new ArrayList<>(commonListTmp);
        }
    }
    

    Driver

    public class FriendDriver {
    
        public static void main(String[] args)  throws Exception{
            args = new String[]{"f:/hadoop/hadoopinput/friends.txt", "f:/hadoop/friendsouput"};
    
            Configuration conf = new Configuration();
    
            Job job = Job.getInstance(conf);
            job.setJarByClass(FriendDriver.class);
    
            job.setMapperClass(FriendsMapper.class);
            job.setReducerClass(FriendsReducer.class);
    
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(Text.class);
    
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(NullWritable.class);
    
            FileInputFormat.setInputPaths(job, new Path(args[0]));
            FileOutputFormat.setOutputPath(job, new Path(args[1]));
    
            boolean result = job.waitForCompletion(true);
    
            System.out.println(result ? 1 : 0);
        }
    }
    

    思路讲解

    首先我们考虑用hadoop中的MapReduce技术解决问题,MapReduce主要分为两个阶段:map阶段和reduce阶段。map阶段负责聚集数据,reduce阶段负责计算数据。

    我们在map阶段,先把数据按照冒号进行切割成如下格式:

    key:A value:B,C,D,F,E,O
    

    将其封装成reduce阶段的key和value,传入到reduce阶段。

    在reduce阶段,我们把所有的key和value存储到一个Map键值对结构中,方便接下来的操作。

    在Reduce的最后结束的cleanup阶段,我们已经有了一个以用户的ID(名字)为key,用户拥有的好友为value的Map,我们对所有人的ID进行一个循环,依次比对每个ID拥有的好友,比如第一个人拥有的好友为集合A,第二个人拥有的好友为集合B,我们对集合A和B取交集,就能获得共同好友了。之后我们对要输出的结果进行一顿拼接,拼接出要输出的结果。最后将其写出即可。
    最后输出的结果为:

    A和A的共同好友为:B C D F E O 
    A和B的共同好友为:C E 
    A和C的共同好友为:D F 
    A和D的共同好友为:F E 
    A和E的共同好友为:B C D 
    A和F的共同好友为:B C D E O 
    A和G的共同好友为:C D F E 
    A和H的共同好友为:C D E O 
    A和I的共同好友为:O 
    A和J的共同好友为:B O 
    A和K的共同好友为:C D 
    A和L的共同好友为:D F E 
    A和M的共同好友为:F E 
    B和A的共同好友为:C E 
    B和B的共同好友为:A C E K 
    B和C的共同好友为:A 
    B和D的共同好友为:A E 
    B和E的共同好友为:C 
    B和F的共同好友为:A C E 
    B和G的共同好友为:A C E 
    B和H的共同好友为:A C E 
    B和I的共同好友为:A 
    B和K的共同好友为:A C 
    B和L的共同好友为:E 
    B和M的共同好友为:E 
    B和O的共同好友为:A 
    C和A的共同好友为:F D 
    C和B的共同好友为:A 
    C和C的共同好友为:F A D I 
    C和D的共同好友为:F A 
    C和E的共同好友为:D 
    C和F的共同好友为:A D 
    C和G的共同好友为:F A D 
    C和H的共同好友为:A D 
    C和I的共同好友为:A 
    C和K的共同好友为:A D 
    C和L的共同好友为:F D 
    C和M的共同好友为:F 
    C和O的共同好友为:A I 
    D和A的共同好友为:E F 
    D和B的共同好友为:A E 
    D和C的共同好友为:A F 
    D和D的共同好友为:A E F L 
    D和E的共同好友为:L 
    D和F的共同好友为:A E 
    D和G的共同好友为:A E F 
    D和H的共同好友为:A E 
    D和I的共同好友为:A 
    D和K的共同好友为:A 
    D和L的共同好友为:E F 
    D和M的共同好友为:E F 
    D和O的共同好友为:A 
    E和A的共同好友为:B C D 
    E和B的共同好友为:C 
    E和C的共同好友为:D 
    E和D的共同好友为:L 
    E和E的共同好友为:B C D M L 
    E和F的共同好友为:B C D M 
    E和G的共同好友为:C D 
    E和H的共同好友为:C D 
    E和J的共同好友为:B 
    E和K的共同好友为:C D 
    E和L的共同好友为:D 
    F和A的共同好友为:B C D E O 
    F和B的共同好友为:A C E 
    F和C的共同好友为:A D 
    F和D的共同好友为:A E 
    F和E的共同好友为:B C D M 
    F和F的共同好友为:A B C D E O M 
    F和G的共同好友为:A C D E 
    F和H的共同好友为:A C D E O 
    F和I的共同好友为:A O 
    F和J的共同好友为:B O 
    F和K的共同好友为:A C D 
    F和L的共同好友为:D E 
    F和M的共同好友为:E 
    F和O的共同好友为:A 
    G和A的共同好友为:C D E F 
    G和B的共同好友为:A C E 
    G和C的共同好友为:A D F 
    G和D的共同好友为:A E F 
    G和E的共同好友为:C D 
    G和F的共同好友为:A C D E 
    G和G的共同好友为:A C D E F 
    G和H的共同好友为:A C D E 
    G和I的共同好友为:A 
    G和K的共同好友为:A C D 
    G和L的共同好友为:D E F 
    G和M的共同好友为:E F 
    G和O的共同好友为:A 
    H和A的共同好友为:C D E O 
    H和B的共同好友为:A C E 
    H和C的共同好友为:A D 
    H和D的共同好友为:A E 
    H和E的共同好友为:C D 
    H和F的共同好友为:A C D E O 
    H和G的共同好友为:A C D E 
    H和H的共同好友为:A C D E O 
    H和I的共同好友为:A O 
    H和J的共同好友为:O 
    H和K的共同好友为:A C D 
    H和L的共同好友为:D E 
    H和M的共同好友为:E 
    H和O的共同好友为:A 
    I和A的共同好友为:O 
    I和B的共同好友为:A 
    I和C的共同好友为:A 
    I和D的共同好友为:A 
    I和F的共同好友为:A O 
    I和G的共同好友为:A 
    I和H的共同好友为:A O 
    I和I的共同好友为:A O 
    I和J的共同好友为:O 
    I和K的共同好友为:A 
    I和O的共同好友为:A 
    J和A的共同好友为:B O 
    J和E的共同好友为:B 
    J和F的共同好友为:B O 
    J和H的共同好友为:O 
    J和I的共同好友为:O 
    J和J的共同好友为:B O 
    K和A的共同好友为:C D 
    K和B的共同好友为:A C 
    K和C的共同好友为:A D 
    K和D的共同好友为:A 
    K和E的共同好友为:C D 
    K和F的共同好友为:A C D 
    K和G的共同好友为:A C D 
    K和H的共同好友为:A C D 
    K和I的共同好友为:A 
    K和K的共同好友为:A C D 
    K和L的共同好友为:D 
    K和O的共同好友为:A 
    L和A的共同好友为:D E F 
    L和B的共同好友为:E 
    L和C的共同好友为:D F 
    L和D的共同好友为:E F 
    L和E的共同好友为:D 
    L和F的共同好友为:D E 
    L和G的共同好友为:D E F 
    L和H的共同好友为:D E 
    L和K的共同好友为:D 
    L和L的共同好友为:D E F 
    L和M的共同好友为:E F 
    M和A的共同好友为:E F 
    M和B的共同好友为:E 
    M和C的共同好友为:F 
    M和D的共同好友为:E F 
    M和F的共同好友为:E 
    M和G的共同好友为:E F 
    M和H的共同好友为:E 
    M和L的共同好友为:E F 
    M和M的共同好友为:E F G 
    O和B的共同好友为:A 
    O和C的共同好友为:A I 
    O和D的共同好友为:A 
    O和F的共同好友为:A 
    O和G的共同好友为:A 
    O和H的共同好友为:A 
    O和I的共同好友为:A 
    O和K的共同好友为:A 
    O和O的共同好友为:A H I J 
    

    总结

    可能我解释的不是很清楚,代码里都有相应的注释,这块确实有点难理解,也不好解释,如果需要的话就多看几遍多理解吧。
    其实这段代码问题还是有的,比如我不知道如何去重(比如A和G的共同好友和G和A的共同好友不就是一回事么),思考了好久也没想出来,只能留个遗憾了。

  • 相关阅读:
    CronJob控制器中的一些绕坑指南
    Redis16个常见使用场景
    golang md5加密和python md5加密比较
    pyton Pillow 把透明背景转成白色背景的方法替换指定背景图片
    golang 语言中常见占位符含义 %s %d %c
    Java 获取图片rgb值 像素矩阵方法
    nginx 配置文件下载 文件下载器
    vue element 动态增加表单并进行表单验证
    浏览器返回的json自动格式化的插件
    springboot 增加跨域配置 解决前端跨域问题
  • 原文地址:https://www.cnblogs.com/wushenjiang/p/13583293.html
Copyright © 2020-2023  润新知