如何在MapReduce中比较来自不同文件的信息?

时间:2019-04-19 21:39:35

标签: java mapreduce

目的是了解文件X与文件y1,y2,...,yn的相似程度。

对于每个文件,我提取信息并将其存储在结构中;假设从文件中我进行字数统计,然后将结果存储在HashMap<String, Integer> wordCount中(还有其他结构存储其他信息)。

所以我需要生成fileX的wordCount;提取fileY的wordCount(预先生成并写入HDFS文件);计算这两个单词的计数有多少相似(我不能逐行区别;我需要百分比相似)。

FileX是固定的,需要与N fileY进行比较。

所以我的想法是:

作业1:计算fileX信息并将其写入HDFS。

Job2(map1-map2的chainMapper):

Map1:读取fileX的HashMap<String, Integer> wordCount;将结构传递给Map2。

Map2:获取2个输入,fileX的结构,fileYs目录的路径。

Map2计算HashMap<String, Integer> wordCountXHashMap<String, Integer> wordCountY的相似度;减速器获取所有相似度的值并对其进行排序。

我已经在Hadoop - The definitive guide of Tom White上和MultipleInputs上在线阅读过,但这不是关于1个映射器的两个输入,而是根据输入来区分映射器。因此,我想问一下如何将两个值转发到单个映射器。我已经考虑过使用分布式缓存,但这对于解决这个问题没有什么用处。最后,如何确保每个映射器获得不同的文件。

我尝试更新全局HashMap<String, Integer> wordCount,但是当新作业开始时,它无法访问该结构(或者更好的是,它为空)。

public class Matching extends Configured implements Tool{

    private static HashMap<String, Integer> wordCountX;

    public static void main(String[] args) throws Exception {

        int res = ToolRunner.run(new Matching(), args);
        System.exit(res);

    } //end main class

    public int run(String[] args) throws Exception {
        ...
    }

}

编辑:

看到的答案是一个很好的解决方案。

我添加结果代码段。

启动工作:

//configuration and launch of job
        Job search = Job.getInstance(getConf(), "2. Merging and searching");
        search.setJarByClass(this.getClass());

        MultipleInputs.addInputPath(search, creationPath, TextInputFormat.class);
        MultipleInputs.addInputPath(search, toMatchPath, TextInputFormat.class);

        FileOutputFormat.setOutputPath(search, resultPath);
        search.setNumReduceTasks(Integer.parseInt(args[2]));

        search.setMapperClass(Map.class);
        search.setReducerClass(Reduce.class);

        search.setMapOutputKeyClass(ValuesGenerated.class);
        search.setMapOutputValueClass(IntWritable.class);
        //TODO
        search.setOutputKeyClass(NullWritable.class);
        search.setOutputValueClass(Text.class);

        return search.waitForCompletion(true) ? 0 : 1;

地图合并(在清理阶段):

@Override
        public void cleanup(Context context) throws IOException, InterruptedException {

            InputSplit split = context.getInputSplit();
            Class<? extends InputSplit> splitClass = split.getClass();

            FileSplit fileSplit = null;
            if (splitClass.equals(FileSplit.class)) {
                fileSplit = (FileSplit) split;
            } else if (splitClass.getName().equals(
                    "org.apache.hadoop.mapreduce.lib.input.TaggedInputSplit")) {
                // begin reflection hackery...

                try {
                    Method getInputSplitMethod = splitClass
                            .getDeclaredMethod("getInputSplit");
                    getInputSplitMethod.setAccessible(true);
                    fileSplit = (FileSplit) getInputSplitMethod.invoke(split);
                } catch (Exception e) {
                    // wrap and re-throw error
                    throw new IOException(e);
                }

                // end reflection hackery
            }

            String filename = fileSplit.getPath().getName();
            boolean isKnown;

            /*
            the two input files are nominated dinamically;
            the file0 has some name "023901.txt",
            the file1 is the output of a precedent MR job, and is
            something like "chars-r-000000"
            */
            if(filename.contains(".txt")) {
                isKnown = false;
            }
            else {
                isKnown = true;
            }

            if(isKnown) { //file1, known

                ValuesGenerated.setName(new Text(name));

                //other values set
                //...

                context.write(ValuesGenerated, new IntWritable(1));

            }
            else { //file0, unknown

                ValuesGenerated.setName(new Text("unknown"));

                //other values set
                //...

                context.write(ValuesGenerated, new IntWritable(0));

            }           
        }

减少阶段:

public static class Reduce extends Reducer<ValuesGenerated, IntWritable, NullWritable, Text> {

            @Override
            public void reduce(ValuesGenerated key, Iterable<IntWritable> values, Context context) 
                    throws IOException, InterruptedException {

                ValuesGenerated known;
                ValuesGenerated unk;

                String toEmit = null;

                for (IntWritable value : values) {

                    if(value.get() == 1) { //known
                        known = key;
                        toEmit = key.toString();
                        toEmit += "\n " + value;
                        context.write(NullWritable.get(), new Text(toEmit));
                    }
                    else { //unknown
                        unk = key;
                        toEmit = key.toString();
                        toEmit += "\n " + value;
                        context.write(NullWritable.get(), new Text(toEmit));
                    }

                }

            }//end reduce

        } //end Reduce class

我遇到了另一个问题,但是我使用此解决方案hadoop MultipleInputs fails with ClassCastException

2 个答案:

答案 0 :(得分:1)

只需添加多个文件输入路径,即可将多个文件输入到同一Mapper。然后,您可以使用mapperContext来识别哪个文件拆分来自哪个文件位置。

基本上,

第1步:MR工作

  • 读取文件1 + 2

  • 在映射器中发出<word, [val1, val2]>(如果从file1中拆分文件,则val1为1,否则为0;对于val2类似)

  • 在reducer中编写哈希图<work, [file1_count, file2_count]>

第2步:合并分片(wordcount不能那么大,应该适合一台计算机),并使用简单的Java作业来创建自定义相似性指标

答案 1 :(得分:0)

您可以使用db甚至不写入全局文件来代替全局文件。

检查频率与HashMap大小的比例并进行比较:

HashMap<String, Integer> similarities = new HashMap<String, Integer>();
int matching = 0
Int totalX = getTotal(wordCountX);
int totalY = getTotal(wordCountY);

wordCountX.forEach((k,v)->{ 
     Integer count = wordCountY.get(k);
    if (count.getIntValue() / totalY == v.getIntValue() / totalX)
        similarities.put(k, Integer.valueOf(v.getIntValue() / totalY);
});