求倒排索引1(修改默认输入组件以记录行号)

概念:
倒排索引(Inverted index),也常被称为反向索引、置入档案或反向档案,是一种索引方法,
被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。
它是文档检索系统中最常用的数据结构

求出每个关键词在哪个文档中的第几行出现了几次

输入数据格式:

有两个文件data1.txt和data2.txt,文件中的内容就是普通的文本。每个单词就是一个关键词

data1.txt的内容:

zhangsan love zhouba
lisi love zhengshi lisi love wujiu
wangwu love zhaoliu
lisi zhouba zhangsan sunqi

data2.txt的内容:

hello zhangsan
hello zhouba
hello lisi

输出数据格式:

huangixaoming data1.txt,2,2;data1.txt,4,1;data2.txt,3,1

意义:

关键词 lisi 在第一份文档 data1.txt 中的第 2 行出现了 2 次
关键词 lisi 在第一份文档 data1.txt 中的第 4 行出现了 1 次
关键词 lisi 在第二份文档 data2.txt 中的第 3 行出现了 1 次

求得的最终结果:

zhengshi    data1.txt,2,1
hello   data2.txt,3,1;data2.txt,2,1;data2.txt,1,1
zhangsan    data2.txt,1,1;data1.txt,4,1;data1.txt,1,1
lisi    data1.txt,4,1;data1.txt,2,2;data2.txt,3,1
wangwu  data1.txt,3,1
zhaoliu data1.txt,3,1
love    data1.txt,3,1;data1.txt,1,1;data1.txt,2,2
sunqi   data1.txt,4,1
zhouba  data1.txt,1,1;data1.txt,4,1;data2.txt,2,1
wujiu   data1.txt,2,1

实现思想

  1. 在setup中获取文件名
  2. 修改默认组件以记录行号。

    在大文件读取时,只能采用改写默认输入组件的方式进行。因为一个大文件会被切分成多个split并行执行,所以在mapper中声明一个属性记录行号的做法,会导致每一个块的每一行都从头算起,不可行

  3. 在map中以单词名称作为key;文件名,行号,出现次数作为value输出。在reduce中进行合并。

MapReduce主体逻辑

package exam_answer.three;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
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.input.FileSplit;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

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

        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);

        job.setJarByClass(InvertedIndexMR_1.class);

        job.setMapperClass(InvertedIndexMR_1_Mapper.class);
        job.setReducerClass(InvertedIndexMR_1_Reducer.class);

        //设置自定义的默认组件
        job.setInputFormatClass(MyTextInputFormat.class);

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

        FileInputFormat.addInputPath(job, new Path("E:\\bigdatatest\\input"));
        Path outputPath = new Path("E:\\bigdatatest\\output");
        FileSystem fs = FileSystem.get(conf);
        if (fs.exists(outputPath)) {
            fs.delete(outputPath, true);
        }
        FileOutputFormat.setOutputPath(job, outputPath);

        boolean isDone = job.waitForCompletion(true);
        System.exit(isDone ? 0 : 1);
    }


    public static class InvertedIndexMR_1_Mapper extends Mapper<LongWritable, Text, Text, Text> {

        private String fileName = null;

        private Text outkey = new Text();
        private Text outvalue = new Text();

        //用于每一行中的单词计数
        private Map<String, Integer> wordsCountMap = new HashMap<String, Integer>();

        @Override
        protected void setup(Context context) throws IOException, InterruptedException {
            /**
             * 获取文件名
             */
            InputSplit inputSplit = context.getInputSplit();
            FileSplit fileSplit = (FileSplit)inputSplit;
            fileName = fileSplit.getPath().getName();
        }

        /**
         * map方法输出的key-value分别是:
         * key :行号
         * value :word1 word2 word3 ...
         */
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String[] split = value.toString().split(" ");

            //把每一个词存入wordsCountMap并计数
            for(String word : split){
                if(wordsCountMap.containsKey(word)){
                    wordsCountMap.put(word, wordsCountMap.get(word) + 1);
                }else{
                    wordsCountMap.put(word, 1);
                }
            }

            //遍历wordsCountMap并输出key-value
            for(String word : wordsCountMap.keySet()){
                outkey.set(word);
                outvalue.set(fileName + "," + key.get() + "," + wordsCountMap.get(word));
                //输出格式:word 文件名,行号,个数   
                context.write(outkey, outvalue);
            }

            //读取一行后清空HashMap
            wordsCountMap.clear();
        }
    }

    public static class InvertedIndexMR_1_Reducer extends Reducer<Text, Text, Text, Text> {

        private Text outvalue = new Text();

        @Override
        protected void reduce(Text key, Iterable<Text> values, Context context)
                throws IOException, InterruptedException {

            StringBuilder sb = new StringBuilder();
            //合并
            for(Text val : values){
                sb.append(val.toString()).append(";");
            }
            //去除最后一个分号
            String newStr = sb.toString().substring(0, sb.toString().length() - 1);
            outvalue.set(newStr);

            //输出格式:hello    data2.txt,3,1;data2.txt,2,1;data2.txt,1,1
            context.write(key, outvalue);
        }

    }
}

自定义输入组件

自定义输入组件可以直接复制TextInputForMat,和LineRecordReader两个类再其上进行改写。

  1. MyTextInputFormat.class

    只需要把createRecordReader()方法中的返回值改成自定义的MyLineRecordReader。读取组件的主体逻辑是在RecordReader中实现的。

    @Override
    public RecordReader<LongWritable, Text> createRecordReader(InputSplit split, TaskAttemptContext context) {
        String delimiter = context.getConfiguration().get("textinputformat.record.delimiter");
        byte[] recordDelimiterBytes = null;
        if (null != delimiter)
            recordDelimiterBytes = delimiter.getBytes(Charsets.UTF_8);
        //修改为自定义的类
        return new MyLineRecordReader(recordDelimiterBytes);
    }
  1. MyLineRecordReader.class
    因为只把map()的输入的key值从行偏移量改成行号,所以主体逻辑不需要变。nextKeyValue()方法是实现读取文件中的每一行数据的实现,在其中把原来设置key值的方法key.set(pos)改为key.set(lineCount),当然先要声明一个属性private long lineCount = 1; 最后还要在while循环中对lineCount++;
private long lineCount = 1;

    public boolean nextKeyValue() throws IOException {
        if (key == null) {
            key = new LongWritable();
        }
//      key.set(pos);
        //把map输入的key设为行号
        key.set(lineCount);

        if (value == null) {
            value = new Text();
        }
        int newSize = 0;
        // We always read one extra line, which lies outside the upper
        // split limit i.e. (end - 1)
        while (getFilePosition() <= end || in.needAdditionalRecordAfterSplit()) {
            if (pos == 0) {
                newSize = skipUtfByteOrderMark();
                //对行数进行累加
                lineCount++;
            } else {
                newSize = in.readLine(value, maxLineLength, maxBytesToConsume(pos));
                pos += newSize;
                //对行数进行累加
                lineCount++;
            }

            if ((newSize == 0) || (newSize < maxLineLength)) {
                break;
            }


            // line too long. try again
            LOG.info("Skipped line of size " + newSize + " at pos " + (pos - newSize));
        }
        if (newSize == 0) {
            key = null;
            value = null;
            return false;
        } else {
            return true;
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值