前段时间一直在准备实习的事情,没有更新项目相关的博客。最近才通过了百度的实习生面试,面试的时候这个小项目助力不少,所以又想来补充一下前面没有分享的一些项目经验。
Hadoop输入输出格式
Hadoop中mapper的输入必须是(key, value)格式的。若输入文件类型为文本格式,这也是默认的输入文件类型。则key是行号,value就是这一行对应的文本。
同理reducer输出的默认格式也是文本,输出的key,value为一行,中间用制表符隔开。
同时,hadoop也提供了更改输入输出格式的接口。比如更改输出为hadoop支持的MapFile可使用以下语句:
job.setOutputFormatClass(MapFileOutputFormat.class);
Hadoop的文件格式
Hadoop有两种基本的存储(key, value)文件格式,分别是SequenceFile和MapFile。
SequeceFile
每一行就是key,value,顺序存储,没有排序。
MapFile
其中包括两个SequenceFile文件,命名为data和index。
注意其中data是按照key排好序的顺序文件,排序主要是为了方便直接根据key二分查找小于等于它的最后一个key的位置。因为一般文件都会很大,所以要把所有key存入内存再二分显然是不太可能的。
因此需要对key进行抽取,hadoop默认是每隔128个key提取一个,然后把它放入index文件,index中的key就是从data中提取的key,value就是这个key在data文件中的位置。
查找的时候,把index放入内存,找出对应的key,然后再在data文件中相应位置及其后面127个key中找到对应的key。
文件格式转换
Nutch输出的链接以及url的文本信息,都是MapFile格式,为了减少耦合或者更利于mapper输入,我们把它转换成text。
package SearchPackage;
import java.io.IOException;
import java.net.URI;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.MapFile;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.util.ReflectionUtils;
import org.apache.hadoop.io.IOUtils;
public class MapFileRead {
private static MapFile.Reader reader = null;
private static Configuration conf = null;
private static FileSystem fs = null;
private static Path path = null;
public MapFileRead(String uri) throws IOException{
//构造函数,便于在其它需要读取MapFile的程序中在开始阶段就新建一个reader接口
this.conf = new Configuration();
this.fs = FileSystem.get(URI.create(uri), conf);
this.path = new Path(uri);
this.reader = new MapFile.Reader(fs, uri, conf);
}
public static double getValue(String url) throws IOException{
//读取上次计算的pageRank值时会不断调用此函数
DoubleWritable value = new DoubleWritable(0);
try {
reader.get(new Text(url), value);
}
finally {
if(value == null) {
return 0;
}
return value.get();
}
}
public static void main(String[] args) throws IOException {
//当以主程序运行时,为读取MapFile再输出重定向到文本即可
String uri = args[0];
conf = new Configuration();
fs = FileSystem.get(URI.create(uri),conf);
path = new Path(uri);
try {
reader = new MapFile.Reader(fs, uri, conf);
WritableComparable key = (WritableComparable) ReflectionUtils.newInstance(reader.getKeyClass(),conf);
Writable value = (Writable) ReflectionUtils.newInstance(reader.getValueClass(),conf);
while(reader.next(key,value)) {
System.out.printf("%s\t%s\n", key, value);
}
}
finally {
IOUtils.closeStream(reader);
}
}
}
以上代码,当直接运行main函数时,会读取mapFile并输出。更重要的是,当新建此类后,可以方便随机读取我们想要的key,value。比如在计算pageRank,迭代时我们需要url上次的pageRank值,这个时候我们就可以把pageRank的输出设置为MapFile格式,在下次运行时新建一个MapFile.Reader,就可以方便地根据url读取它的pageRank值。
实际运行发现这个读取的效率还是不够理想,在面试的时候面试官也提到了,其实更简单的方法是直接把pageRank值放在url的旁边,但是这需要一定的预处理或者脚本处理,在接下来的改进中可以考虑加入。