hadoop虽然内置了很多个inputFormat但是,有时候还是不满足我们的一些需求,所以我们需要重写一个类,来实现我们对数据的读取。
例如WC我们制定几行一起读取:
所有InputFormat都要直接或间接的继承InputFormat抽象类。
InputFormat接 口中主要定义了如下两个方法:
/**
生产InputSplit集合的方法
此方法接受JobContext接受环境信息,得到要处理的文件信息后,进行逻辑切割,产生InputSplit集合返回
*/
List getSplits(JobContext context) throws IOException, InterruptedException;
/**
此方法返回RecordReader对象。
一个RecordReader包含方法描述如何从InputSplit 切分出要送入 Mapper 的K1 V1对
*/
public abstract RecordReader createRecordReader(InputSplit split,TaskAttemptContext context) throws IOException, InterruptedException;
我们可以直接继承InputFormat,但更多的时候我们会选择继承他的一个实现子类,比如FileInputFormat -- 此类是所有来源为文件的InputFormat的基类,默认的TextInputFormat就继承自它。
FileInputFormat实现了InputFormat接口,实现了getSplits方法,根据配置去逻辑切割文件,返回FileSplit的集合,并提供了isSplitable()方法,子类可以通过在这个方法中返回boolean类型的值表明是否要对文件进行逻辑切割,如果返回false则无论文件是否超过一个Block大小都不会进行切割,而将这个文件作为一个逻辑块返回。而对createRecordReader方法则没有提供实现,设置为了抽象方法,要求子类实现。
如果想要更精细的改变逻辑切块规则可以覆盖getSplits方法自己编写代码实现。 而更多的时候,我们直接使用父类中的方法而将精力放置在createRecordReader上,决定如何将InputSplit转换为一个个的Recoder
inputFormat hadoop的两个jar包都提供了,org.apache.hadoop.mapred是老版本的API,是一个接口,mapreduce是抽象类,它们互相之间不兼容,所以我们用新的。
但是我们要是读取文件的话,可以直接使用fileinputformat 它里面也是重写了getSplits方法,但是他没有另一个RecordReader方法,那就需要我们使用TextInputFormat FileInputFomat
public List getSplits(JobContext job) throws IOException {
//首先是开启了一个监听
StopWatch sw = new StopWatch().start();
获取切片的大小,(默认的大小1,从我们的job设置的切片最小值)取最大值作为最小值
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
long maxSize = getMaxSplitSize(job);
// generate splits
List splits = new ArrayList();
//后去当前job代表的所有的文件类。它实现类Writable, Comparable两个类
List files = listStatus(job);
//遍历所有文件
for (FileStatus file: files) {
Path path = file.getPath();
long length = file.getLen();
//如果文件有内容
if (length != 0) {
BlockLocation[] blkLocations;
if (file instanceof LocatedFileStatus) {
//如果是本地文件,直接获取文件块
blkLocations = ((LocatedFileStatus) file).getBlockLocations();
} else {
FileSystem fs = path.getFileSystem(job.getConfiguration());
blkLocations = fs.getFileBlockLocations(file, 0, length);
}
//下面的这个方法默认返回了true,我们可以自定义这个方法覆盖他,然后就可以控制文件是否切割,如果返回false,那么文件就不会进行切割了。
if (isSplitable(job, path)) {
long blockSize = file.getBlockSize();
long splitSize = computeSplitSize(blockSize, minSize, maxSize);
long bytesRemaining = length;
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, splitSize,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
bytesRemaining -= splitSize;
}
if (bytesRemaining != 0) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
}
} else { // not splitable
splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts(),
blkLocations[0].getCachedHosts()));
}
} else {
//Create empty hosts array for zero length files
splits.add(makeSplit(path, 0, length, new String[0]));
}
}
// Save the number of input files for metrics/loadgen
job.getConfiguration().setLong(NUM_INPUT_FILES, files.size());
sw.stop();
if (LOG.isDebugEnabled()) {
LOG.debug("Total # of splits generated by getSplits: " + splits.size()
+ ", TimeTaken: " + sw.now(TimeUnit.MILLISECONDS));
}
return splits;
}
TextInputFormat 它又实现了fileInputFormat没有实现的RecordReader方法,重写了父类需要我们覆盖的isSplitable方法。
@InterfaceAudience.Public
@InterfaceStability.Stable
public class TextInputFormat extends FileInputFormat
implements JobConfigurable {
private CompressionCodecFactory compressionCodecs = null;
public void configure(JobConf conf) {
compressionCodecs = new CompressionCodecFactory(conf);
}
protected boolean isSplitable(FileSystem fs, Path file) {
final CompressionCodec codec = compressionCodecs.getCodec(file);
if (null == codec) {
return true;
}
return codec instanceof SplittableCompressionCodec;
}
public RecordReader getRecordReader(
InputSplit genericSplit, JobConf job,
Reporter reporter)
throws IOException {
reporter.setStatus(genericSplit.toString());
String delimiter = job.get("textinputformat.record.delimiter");
byte[] recordDelimiterBytes = null;
if (null != delimiter) {
recordDelimiterBytes = delimiter.getBytes(Charsets.UTF_8);
}
//下面这个方法中封装了我们需要的行读取器,然后读取,
return new LineRecordReader(job, (FileSplit) genericSplit,
recordDelimiterBytes);
}
}
重写MyRecordReader
public class MyRecordReader extends RecordReader {
//
private FileSplit split=null;
private Path path=null;
private LineReader lineReader=null;
private Text key=null;
private Text value=null;
private boolean haseNo=true;
@Override
public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
this.split=(FileSplit) split;
this.path=((FileSplit) split).getPath();
Configuration conf=context.getConfiguration();
FileSystem fileSystem = path.getFileSystem(conf);
FSDataInputStream in = fileSystem.open(path);
lineReader = new LineReader(in);
}
//每次调用这个方法得到下一个key和value的值,但是我们目前是想要四行作为一个key和value,所以,
//需要自此你故意返回的keyvalue,但是这个input在init方法中,所以需要定一个全局变量来得到。
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
//读取的行数
key=new Text();
value=new Text();
//处理可能出现文件不全的情况
//定义一个计数器,记录本次读取到了多少行
int count=0;
//定义了一个temp的临时记录内容
Text temp=new Text();
for(int i=0;i<4;i++){
int num = lineReader.readLine(temp);
//没有读取到数据
if(num==0){
//不能直接返回false,因为有可能其他行读取到了数据
haseNo=false;
break;
}else{
value.append(temp.getBytes(),0,temp.getLength());
//要是几行合并读取,需要人为的给每行读完加一个换行
value.append("\r\n".getBytes(),0,"\r\n".length());
count++;
}
//表示没有读取导数据
if(count==0){
return false;
}else {
key.set(count+"");
return true;
}
}
return false;
}
@Override
public Text getCurrentKey() throws IOException, InterruptedException {
return key;
}
@Override
public Text getCurrentValue() throws IOException, InterruptedException {
return value;
}
@Override
public float getProgress() throws IOException, InterruptedException {
return 0;
}
@Override
public void close() throws IOException {
if(lineReader!=null){
lineReader.close();
}
}
}