HDFS:
比如一个100TB的文件装入HDFS集群。
并行处理切分文件就非常重要。因为每台机器都保存着大文件的一部分,则从文件中间开始处理文件就很重要。
Hadoop的文件系统提供了FSDataInputStream类,而未使用DataInputStream类,主要因为FSD实现了文件的随机读写功能。
这样每个分片都由它所驻留的机器进行处理,就自动实现了并行。
流程为 文件(100TB) --> 分片( Input Split ) (小于HDFS的块大小)(逻辑划分,不同于HDFS块是物理划分) --> 记录 (一对键和值) --> 键值对
如果一个记录跨越了一个节点,它会通过网络从另一个节点获取记录的剩余片段。
所以所有大于块大小的文件都需要分片。
每个map任务分配一个分片。 每个map( ) 输入一对键值。
InputFormat接口:
Hadoop 分割与读取输入文件的方式被定义在InputFormat接口中。
默认实现为TextInputFormat , 这种方式 一行为一个记录,键值对为 每行的字节偏移量 和 该行的Text 。
getSplits( ) 确定输入文件,并将之分片。
类FileInputFormat实现InputFormat接口,并实现了getSplits( ) , 把输入数据分为一组分片,分片数目在numSplits 中限定。
FIF类还有isSplitable( ) 方法,规定此文件能否分片。(比如一些压缩文件不能分片)
getRecordReader( ) 负责把输入分片解析为记录,再把记录解析为键值对。
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.FileInputFormat;
import org.apache.hadoop.mapred.FileSplit;
import org.apache.hadoop.mapred.InputSplit;
import org.apache.hadoop.mapred.JobClient;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.KeyValueLineRecordReader;
import org.apache.hadoop.mapred.MapReduceBase;
import org.apache.hadoop.mapred.RecordReader;
import org.apache.hadoop.mapred.Reporter;
import org.apache.hadoop.mapred.FileOutputFormat;
import org.apache.hadoop.io.Writable;
class URLWritable implements Writable{
protected URL url;
public URLWritable(){
}
public URLWritable(URL a){
this.url = a;
}
@Override
public void readFields(DataInput in) throws IOException {
this.url = new URL(in.readUTF());
// UTF形式传输
}
@Override
public void write(DataOutput out) throws IOException {
out.writeUTF(url.toString());
}
public void set (String s) throws MalformedURLException{
url = new URL(s);
}
}
class UrlLineRecordReader implements RecordReader<Text, URLWritable>{
private KeyValueLineRecordReader lineReader;
// 基于KeyValueLineRecordReader 类的包装
private Text linekey, linevalue;
public UrlLineRecordReader(JobConf job,FileSplit split) throws IOException{
lineReader = new KeyValueLineRecordReader(job, split);
linekey = lineReader.createKey();
linevalue = lineReader.createValue();
}
@Override
public void close() throws IOException {
lineReader.close();
}
@Override
public Text createKey() {
return new Text("");
}
@Override
public URLWritable createValue() {
return new URLWritable();
}
@Override
public long getPos() throws IOException {
return lineReader.getPos();
}
@Override
public float getProgress() throws IOException {
return lineReader.getProgress();
}
@Override
public boolean next(Text arg0, URLWritable arg1) throws IOException {
if(!lineReader.next(linekey, linevalue)){
return false;
}
arg0.set(linekey);
arg1.set(linevalue.toString());
return true;
}
}
class UrlTextInputFormat extends FileInputFormat<Text, URLWritable>{
@Override
public RecordReader<Text, URLWritable> getRecordReader(InputSplit arg0, JobConf arg1, Reporter arg2)
throws IOException {
// TODO Auto-generated method stub
return new UrlLineRecordReader(arg1,(FileSplit)arg0);
}
}
整体思路为 现在要重写InputFormat,来自定义分割文件,解析成键值对的过程。
重写InputFotmat需要重写两个函数。 getSplits( )函数来改变分片格式,getRecordReader( ) 决定生成键值对的过程。
FIleInputFormat 实现了getSplits( ) , 则如果只想简单改变键值,就可继承自FileInputFormat。 自己只需实现getRecordReader( ).
getRecordReader( ) 返回值为 RecordReader< K,V> 类型,RR< K,V > 类型有6个函数需要实现。
这里通过在KeyValueLineRecordReader基础上包装实现了自己的RR<K,V>类。 KVLRR是Hadoop实现的KeyValueTextInputFornat类中的RecordReader实现。
这里实现自己的RecordReader类时需要URLWritable,则实现了这个类。