十七、KeyValueTextInputFormat实操与源码分析

一、介绍

        KeyValueTextInputFormat是FileInputFormat的实现类,所以kv的分片机制是由父类决定的,既然是子类那么必然有不一样的地方。不同之处是它的读取数据规则不同。

KV的读取数据特点:
       KV读取每一行均为一条记录,被分隔符分割为key,value。可以通过在驱动类中设置conf.set(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR, “\t”);来设定分隔符。默认分隔符是tab(\t),当然也可以设置为其它字符,主要看处理的数据格式。

假设如下数据:
如果设置KEY_VALUE_SEPERATOR为 \t,那么读取的每行数据格式为
(java,java hello scala java)
(baidu,alib meituan scala baidu)

以第一个分割符前部分为key,余下所有为value

java	java	hello	scala	java
baidu	alib	meituan	scala	baidu
alib	java	scala	java	alib
baidu	alib	meituan	scala	baidu
alib	java	scala	java	alib
baidu	alib	meituan	scala	baidu
alib	java	scala	java	alib

二、代码操作

需求:分析如上数据第一个单词相同的数据行数?

1、mapper
package com.cjy.mr.kvformat;

import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

public class KVTextMapper extends Mapper<Text, Text, Text, LongWritable>{

    // 1 设置value
    LongWritable v = new LongWritable(1);

    @Override
    protected void map(Text key, Text value, Context context)
            throws IOException, InterruptedException {

//java	java	hello	scala	java

        // 2 (java,1)
        context.write(key, v);
    }
}
2、reducer
package com.cjy.mr.kvformat;

import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

public class KVTextReducer extends Reducer<Text, LongWritable, Text, LongWritable>{

    LongWritable v = new LongWritable();

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

        long sum = 0L;

        // 1 汇总统计
        for (LongWritable value : values) {
            sum += value.get();
        }

        v.set(sum);

        // 2 输出
        context.write(key, v);
    }
}
3、driver
package com.cjy.mr.kvformat;

import java.io.IOException;
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.input.KeyValueLineRecordReader;
import org.apache.hadoop.mapreduce.lib.input.KeyValueTextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class KVTextDriver {

    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        Configuration conf = new Configuration();


        // 设置切割符 \t 为默认分隔符,可以不用设置
//        conf.set(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR, "\t");
        // 1 获取job对象
        Job job = Job.getInstance(conf);

        // 2 设置jar包位置,关联mapper和reducer
        job.setJarByClass(KVTextDriver.class);
        job.setMapperClass(KVTextMapper.class);
        job.setReducerClass(KVTextReducer.class);

        // 3 设置map输出kv类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(LongWritable.class);

        // 4 设置最终输出kv类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(LongWritable.class);

        // 5 设置输入输出数据路径
        FileInputFormat.setInputPaths(job, new Path("/Users/chenjunying/Downloads/wd.txt"));
        // 设置输入格式
        job.setInputFormatClass(KeyValueTextInputFormat.class);

        // 6 设置输出数据路径
        FileOutputFormat.setOutputPath(job, new Path("/Users/chenjunying/Downloads/out/"));

        // 7 提交job
        job.waitForCompletion(true);
    }
}
4、本地运行结果
alib	3
baidu	3
java	1


三、分割kv数据源码分析

1、首先看一下KeyValueTextInputFormat,继承了FileInputFormat,并没有getSplits方法,所以使用的分片机制未变,主要就是RecordReader 它是怎么读取数据的。

public class KeyValueTextInputFormat extends FileInputFormat<Text, Text> {
    public KeyValueTextInputFormat() {
    }
	
    protected boolean isSplitable(JobContext context, Path file) {
        CompressionCodec codec = (new CompressionCodecFactory(context.getConfiguration())).getCodec(file);
        return null == codec ? true : codec instanceof SplittableCompressionCodec;
    }
    
	//重写了片数据读取方式,下面就来看看它是怎么读取数据的
    public RecordReader<Text, Text> createRecordReader(InputSplit genericSplit, TaskAttemptContext context) throws IOException {
        context.setStatus(genericSplit.toString());
        return new KeyValueLineRecordReader(context.getConfiguration());
    }
}

2、KeyValueLineRecordReader查看

	//1.默认数据分隔符
    public KeyValueLineRecordReader(Configuration conf) throws IOException {
        String sepStr = conf.get("mapreduce.input.keyvaluelinerecordreader.key.value.separator", "\t");
        this.separator = (byte)sepStr.charAt(0);
    }


	//2.这是主要加载数据的函数
	//lineRecordReader这里其实是按照分片的起止偏移量加载的所有行数据存放位置,具体它是怎么加载进来的这里就不介绍了。
	
public synchronized boolean nextKeyValue() throws IOException {
     byte[] line = null;
     int lineLen = true;
     //1.类似迭代器,获取下行数据
     if (this.lineRecordReader.nextKeyValue()) {
     //2.获取的当前行数据
         this.innerValue = this.lineRecordReader.getCurrentValue();
    //3.获取当前行数据的字节数组
         byte[] line = this.innerValue.getBytes();
         //4.获取当前行数据长度
         int lineLen = this.innerValue.getLength();
         //5.判断当前行数据为空
         if (line == null) {
             return false;
         } else {
         //6.当前行数据不为空,准kv键值对
             if (this.key == null) {
                 this.key = new Text();
             }

             if (this.value == null) {
                 this.value = new Text();
             }
//7.计算kv分割的坐标位置
             int pos = findSeparator(line, 0, lineLen, this.separator);
             //9.分割数据开始为kv赋值
             setKeyValue(this.key, this.value, line, lineLen, pos);
             return true;
         }
     } else {
         return false;
     }
 }

	//计算key分割位置
 public static int findSeparator(byte[] utf, int start, int length, byte sep) {
      for(int i = start; i < start + length; ++i) {
      //8.判断分隔符所在字节偏移量
          if (utf[i] == sep) {
              return i;
          }
      }

      return -1;
  }


//根据pos偏移量开始分割数据,为kv赋值
public static void setKeyValue(Text key, Text value, byte[] line, int lineLen, int pos) {
    if (pos == -1) {
        key.set(line, 0, lineLen);
        value.set("");
    } else {
        key.set(line, 0, pos);
        value.set(line, pos + 1, lineLen - pos - 1);
    }

}
便利迭代数据片内每行数据
计算分割符号的字节偏移量坐标
根据分割符号偏移量位置拆分数据并对kv进行赋值


四、debug案例分析运算过程

以第一行数据读取为例:

java	java	hello	scala	java
1、获取当前第一行数据,分割符号 \t,字节数据

在这里插入图片描述

2、计算出分割坐标是4,
坐标计算函数没打断点,下面能看到计算结果
<br>
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200510232944164.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1MjQxMDgw,size_16,color_FFFFFF,t_70#pic_center)
3、map读取到的数据

已经可以看到是分割后的kv键值对数据了
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值