学习MapReduce(五),自定义InputFormat类

    2017.3.13继续学习MapReduce。昨天天学习了Partition的自定义,Combiner的自定义,InputFormat用了很长时间才算是马马虎虎有点感觉。默认使用的InputFormat类是底层提供的FileInputFormat,FileInputFormat使用的是默认的RecordReader,我们需要重写createRecordReader,返回自己写的RecordReader。

    RecordReader可以规定按照自己规定的K,V值传给Mapper类的输入K,V。比如:我们想在本地Map中对海量的小文件合并成一个大文件,我们可以规定文件名为K,文件里的全内容为V传给Mapper。代码如下:

----------------->>>自己写的RecordReader

package lcPhoneFolw.inputformat;

import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
/**
 * 将会传入一个k,v值
 * @author admin
 *
 */
public class MyRecordReaderDemo extends RecordReader {
	//第一次看这玩意 一脸蒙蔽啊,RecordReader是用来干什么,为什么要重写,根本没概念,不写了,去百度去了
	//百度进修回来了,看了一圈,还在蒙蔽中,只是有点感悟了,如:我们默认使用的LineRecordReader,和TextInputFormat
	//LineRecordReader是对一个文件一行一行的返回V值,以行的偏移量为(就是行号)K值返回给Map。我们写自己的RecordReader
	//无非就是改变给Map的K,V值。这个案例是将一整个文件当作V值返回给Map
	private boolean flag = false; //这个有什么用我是真的不知道---》可能以后会搞明白以后补充
	private FileSplit filesplit; //这个是一个文件类的实例 必须要
	private Configuration conf;//这个必须要
	private Text key;
	private Text value = new Text();//这个是返回给Map的V值
	
	/**
	 * 初始化(属于重写,参数是一个输入流,和一个Context。Context是hadoop中的上下文类,可以通过这个类获得流程的所有信息,所以我们可以通过他
	 * 来得到一个Configuration的一个实例。)
	 */
	@Override
	public void initialize(InputSplit split, TaskAttemptContext context)
			throws IOException, InterruptedException {
		this.filesplit = (FileSplit)split;//将输入流转化为文件类
		this.conf = context.getConfiguration();//从上下文类获得Configuration实例
		
		
	}

	@Override
	public boolean nextKeyValue() throws IOException, InterruptedException {
		if(!flag){
			byte[] b = new byte[(int)filesplit.getLength()];//新建一个文件字节长度的字节数组
			Path path = filesplit.getPath();//手里拿着文件类,是不是可以获得文件路径(目前还不知道获取路径是问了干什么)
			FileSystem fs = path.getFileSystem(conf);//原来是为了得到一个FileSystem的实例啊
			FSDataInputStream fis = fs.open(path);//通过FileSystem打开这个文件
			//这里我好像出问题了,我得到的输入流是一个byte类型的。。。。我返回的V值却是一个Text类型的
			IOUtils.readFully(fis,b,0,b.length);//这里我将文件中的内容写入b中
			value = new Text(new String(b));//将数组转换为字符串,在将字符串转换成一个Text
			key = new Text(path.toString());//将文件名当作key
			fis.close();//关闭流
			flag = true;//不知道为啥  --》以后补充
			return true;//不知道为啥 --》以后补充
		}
		return false;
	}

	@Override
	public Text getCurrentKey() throws IOException, InterruptedException {
		// TODO Auto-generated method stub
		return key;
	}

	@Override
	public Text getCurrentValue() throws IOException, InterruptedException {
		// TODO Auto-generated method stub
		return value;
	}

	@Override
	public float getProgress() throws IOException, InterruptedException {
		// TODO Auto-generated method stub
		return flag?1.0f:0.0f;
	}

	@Override
	public void close() throws IOException {
		
	}

}

以上代码可以实现以文件名为K,文件全内容为V传给Map。然后,开始----写自己的InputFormat类:

package lcPhoneFolw.inputformat;

import java.io.IOException;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.JobContext;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
/*
 * 自己定义的InputFormat类,这个类的主要作用是用来返回自己定义RecordReader
 */
public class MyInuputFormat extends FileInputFormat{
	
	
	//约束文件是否是可切片的,这里返回false,表示不可切片
	@Override
	protected boolean isSplitable(JobContext context, Path filename) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public RecordReader createRecordReader(InputSplit split,
			TaskAttemptContext context) throws IOException,
			InterruptedException {
		MyRecordReaderDemo mrd = new MyRecordReaderDemo();
		mrd.initialize(split, context);
		return mrd;
	}

}

    其实写自己的InputFormat类就是重写了一个createRecordReader方法,这个方法的返回值为RecordReader,返回自己写的RecordReader就可以了。因为我们想要合并文件,所以重写isSplitable返回false约束文件不能被切片。

    然后写自己的实现类,这个实现类继承了Configured并实现了Tool接口,这个类主要是重写run方法。当然,写run方法之前需要些一个Mapper类来实现map(毕竟我们需要map来运行计算不是?)

-------------------------->>>>自己写的Mapper类:

package lcPhoneFolw.inputformat;

import java.io.IOException;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

public class SequenceFileMapper extends Mapper<Text,Text,Text,Text> {

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

	}
	
}

这个Mapper类非常简单,接受来自RecordReader传过来的K,V值,输出相应的K,V值

------------------------>>>自己的实现类

package lcPhoneFolw.inputformat;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

/**
 * 这个类写的是本地通过mapreduce来处理文件的合并的,需要继承Configured和实现Tool
 * Configured是什么,Tool又是什么?,字面意思Configured和Configuration一个意思?
 * Tool--->>工具?百度进修去。。。。。。。。
 * 大概明白了,这个类主要是为了运行本地计算,诸如:小文件合并成一个大文件,等等。重写的run方法可以
 * 写一个Job,这个Job可以只有Map,也可以没有job,只得到想要的东西。也可以mapreduce都写。。。我是这样理解的。
 * 下面这个是将无数小文件合并成一个大文件。
 * @author admin
 *
 */
public class SmallFileToBigFile extends Configured implements Tool {
	
	public int run(String[] args) throws Exception {
		Configuration conf = new Configuration();//新建一个Configuration实例
//		System.setProperty("HADOOP_USER_NAME", "hdfs");//如果本地环境变量已配置,可以不写
//		String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
//		if (otherArgs.length != 2) {
//			System.err.println("Usage: combinefiles");
//			System.exit(2);
//		}   这么一大堆东西只是判断输入的参数是否是两个,写上去更好。(主要是制定输入路径和输出路径)
		//新建一个Job实例
		Job job = Job.getInstance(conf,"combine small files to sequencefile");
		//设定文件输入格式为自己规定的输入格式(如果没有设定,也可以合并成一个文件,但是这个文件还是按照一行一行的读取的。
		//但是如果想要成功运行,自己写的Mapper类必须使用LongWritable为输入K)
		job.setInputFormatClass(MyInuputFormat.class);
		//设定输出格式------>这个也可以自己写,还没研究,这里使用底层提供的SequenceFileOutputFormat类
		job.setOutputFormatClass(SequenceFileOutputFormat.class);
		//设定自己定义的Mapper类
		job.setMapperClass(SequenceFileMapper.class);
		//设定输出K,V值,这里没有Reduce所以可以直接使用setOutputKeyClass和setOutputValueClass
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(Text.class);
		//设定输入输出文件路径
		MyInuputFormat.setInputPaths(job,new Path(args[0]));
		FileOutputFormat.setOutputPath(job,new Path(args[1]));
		//返回提交状态
		return job.waitForCompletion(true) ? 0 : 1;

	}
	public static void main(String[] args) throws Exception {
		//运行run方法
		int exitCode = ToolRunner.run(new SmallFileToBigFile(),args);
		System.exit(exitCode);
	}
		

}

    代码中我已经写的很清楚了,重新run方法,其实就是提交了一个Job,这个Job规定了Mapper类,输入格式和输出格式,其中输入格式就是自己规定的InputFormat,也就是说在运行这个代码的时候,文件先进入自己写InputFoamat中实现自己规定的K,V输出。然后交个Mapper,Mapper开始计算,输出格式按照底层提供的类输出。

    大概就是这样。。。。。谢谢观看

 

 

转载于:https://my.oschina.net/u/3294842/blog/857399

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值