【问题标题】:Computing set intersection and set difference of the records of two files with hadoop用hadoop计算两个文件记录的集交集和集差
【发布时间】:2011-09-22 02:06:20
【问题描述】:

很抱歉在 hadoop 用户邮件列表和此处交叉发布此内容,但这对我来说是一件紧迫的事情。

我的问题如下: 我有两个输入文件,我想确定

  • a) 仅出现在文件 1 中的行数
  • b) 仅在文件 2 中出现的行数
  • c) 两者共有的行数(例如关于字符串相等性)

例子:

File 1:
a
b
c

File 2:
a
d

每种情况的期望输出:

lines_only_in_1: 2         (b, c)
lines_only_in_2: 1         (d)
lines_in_both:   1         (a)

基本上我的做法如下: 我编写了自己的 LineRecordReader,以便映射器接收由行(文本)和指示源文件(0 或 1)的字节组成的对。 映射器仅再次返回该对,因此实际上它什么也不做。 然而,副作用是,组合器接收到一个

Map<Line, Iterable<SourceId>>

(其中 SourceId 为 0 或 1)。

现在,对于每一行,我可以获得它出现的一组来源。因此,我可以编写一个组合器来计算每种情况(a、b、c)的行数(清单 1)

然后组合器仅在清理时输出“摘要”(这样安全吗?)。 所以这个摘要看起来像:

lines_only_in_1   2531
lines_only_in_2   3190
lines_in_both      901

然后,在 reducer 中,我只对这些摘要的值求和。 (所以 reducer 的输出看起来和 combiner 的输出一样)。

但是,主要问题是,我需要将两个源文件视为一个虚拟文件,它会产生表单的记录 (line, sourceId) // sourceId 0 或 1

我不知道如何实现。 所以问题是我是否可以避免预先处理和合并文件,并使用虚拟合并文件阅读器和自定义记录阅读器之类的东西即时进行。 非常感谢任何代码示例。

最好的问候, 克劳斯

清单 1:

public static class SourceCombiner
    extends Reducer<Text, ByteWritable, Text, LongWritable> {

    private long countA = 0;
    private long countB = 0;
    private long countC = 0; // C = lines (c)ommon to both sources

    @Override
    public void reduce(Text key, Iterable<ByteWritable> values, Context context) throws IOException, InterruptedException {
        Set<Byte> fileIds = new HashSet<Byte>();
        for (ByteWritable val : values) {
            byte fileId = val.get();

            fileIds.add(fileId);
        }

        if(fileIds.contains((byte)0)) { ++countA; }
        if(fileIds.contains((byte)1)) { ++countB; }
        if(fileIds.size() >= 2) { ++countC; }
    }

    protected void cleanup(Context context)
            throws java.io.IOException, java.lang.InterruptedException
    {
        context.write(new Text("in_a_distinct_count_total"), new LongWritable(countA));
        context.write(new Text("in_b_distinct_count_total"), new LongWritable(countB));
        context.write(new Text("out_common_distinct_count_total"), new LongWritable(countC));
    }
}

【问题讨论】:

    标签: java hadoop set-intersection


    【解决方案1】:

    好的,我必须承认,到目前为止,我并没有真正理解您所尝试的要点,但我有一个简单的方法来完成您可能需要的工作。

    看看文件映射器。这将获取文件名并与输入的每一行一起提交。

        public class FileMapper extends Mapper<LongWritable, Text, Text, Text> {
    
            static Text fileName;
    
            @Override
            protected void map(LongWritable key, Text value, Context context)
                    throws IOException, InterruptedException {
                context.write(value, fileName);
            }
    
            @Override
            protected void setup(Context context) throws IOException,
                    InterruptedException {
    
                String name = ((FileSplit) context.getInputSplit()).getPath().getName();
                fileName = new Text(name);
            }
        }
    

    现在我们有一堆看起来像这样的键/值(关于您的示例)

        a File 1
        b File 1
        c File 1
    
        a File 2
        d File 2
    

    显然减少它们会让你得到这样的输入:

        a File 1,File 2
        b File 1
        c File 1
        d File 2
    

    您需要在 reducer 中执行的操作可能如下所示:

    public class FileReducer extends Reducer<Text, Text, Text, Text> {
    
        enum Counter {
            LINES_IN_COMMON, LINES_IN_FIRST, LINES_IN_SECOND
        }
    
        @Override
        protected void reduce(Text key, Iterable<Text> values, Context context)
                throws IOException, InterruptedException {
            HashSet<String> set = new HashSet<String>();
            for (Text t : values) {
                set.add(t.toString());
            }
    
            // if we have only two files and we have just two records in our hashset
            // the line is contained in both files
            if (set.size() == 2) {
                context.getCounter(Counter.LINES_IN_COMMON).increment(1);
            } else {
                // sorry this is a bit dirty...
                String t = set.iterator().next();
                // determine which file it was by checking for the name:
                if(t.toString().equals("YOUR_FIRST_FILE_NAME")){
                    context.getCounter(Counter.LINES_IN_FIRST).increment(1);
                } else {
                    context.getCounter(Counter.LINES_IN_SECOND).increment(1);
                }
            }
        }
    
    }
    

    您必须将 if 语句中的字符串替换为您的文件名。

    我认为使用作业计数器比使用自己的原语并在清理时将它们写入上下文要清晰一些。您可以通过在完成后调用这些东西来检索作业的计数器:

    Job job = new Job(new Configuration());
    //setup stuff etc omitted..
    job.waitForCompletion(true);
    // do the same line with the other enums
    long linesInCommon = job.getCounters().findCounter(Counter.LINES_IN_COMMON).getValue();
    

    无论如何,如果您需要 HDFS 中的共同行数等,请选择您的解决方案。

    希望对你有所帮助。

    【讨论】:

    • 嗨,我有点不清楚:关键是,我希望组合器只将摘要(1、2 和 common 中的行数)交给减速器 - 没有需要将所有行都发送回减速器。但要使其工作,组合器必须同时查看两个文件的记录(我的 RecordReader 已经生成 (line, fileId) 对;从文件名到 fileId 的映射与配置对象一起传递)。但是,当使用两个 FileInputFormat.addInputPath(job, file) 语句添加文件时,文件会单独处理,因此组合器看不到它们的“联合”。
    • puh 这真是一个奇怪的“优化”。但好点。
    • 抱歉回复晚了;有没有可能,我的想法行不通:源文件被分割,分割被发送到节点。然后节点从其相应的拆分中读取记录。因此,源文件中的重复记录可能位于多个拆分中,因此分布在多个节点上。因此,只有在 reducer 中才能对重复项进行分组。这是正确的吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-03-20
    • 1970-01-01
    • 1970-01-01
    • 2020-07-14
    • 1970-01-01
    • 2011-11-26
    • 1970-01-01
    相关资源
    最近更新 更多