1. Wordcount
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class WordCountExample {
private static class WordCountMapper extends Mapper{
@Override
protected void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
String str=value.toString();
String []strArray=str.split(" " );
for (String s:strArray){
context.write(new Text(s), new IntWritable(1 ));
}
}
}
private static class WordCountReducer extends Reducer{
@Override
protected void reduce(Text key, Iterable values,
Context context)
throws IOException, InterruptedException {
int sum=0 ;
for (IntWritable count:values){
sum+=count.get();
}
context.write(key, new IntWritable(sum));
}
}
public static void main(String[] args) throws Exception{
Configuration conf=new Configuration();
String []argArray=new GenericOptionsParser(conf,args).getRemainingArgs();
if (argArray.length!=2 ){
System.out.println("需要两个参数" );
System.exit(1 );
}
Job job=Job.getInstance(conf,"wordcount" );
job.setJarByClass(WordCountExample.class );
job.setMapperClass(WordCountMapper.class );
job.setMapOutputKeyClass(Text.class );
job.setMapOutputValueClass(IntWritable.class );
job.setOutputKeyClass(Text.class );
job.setOutputValueClass(IntWritable.class );
job.setReducerClass(WordCountReducer.class );
FileInputFormat.addInputPath(job, new Path(argArray[0 ]));
FileOutputFormat.setOutputPath(job, new Path(argArray[1 ]));
System.exit(job.waitForCompletion(true )?0 :1 );
}
}
2. 去重
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class DeleteRepeatExample {
private static class DeleteRepeatMapper extends Mapper{
@Override
protected void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
context.write(value, new IntWritable(0 ));
}
}
private static class DeleteRepeatReducer extends Reducer{
@Override
protected void reduce(Text key, Iterable values,
Context context)
throws IOException, InterruptedException {
context.write(key, null );
}
}
public static void main(String[] args) throws Exception{
Configuration conf=new Configuration();
String[]argArray=new GenericOptionsParser(conf, args).getRemainingArgs();
if (argArray.length!=2 ){
System.out.println("请提供两个参数" );
System.exit(1 );
}
Job job=Job.getInstance(conf,"delete repeat" );
job.setJarByClass(DeleteRepeatExample.class );
job.setMapperClass(DeleteRepeatMapper.class );
job.setMapOutputKeyClass(Text.class );
job.setMapOutputValueClass(IntWritable.class );
job.setReducerClass(DeleteRepeatReducer.class );
job.setOutputKeyClass(Text.class );
job.setOutputValueClass(Object.class );
FileInputFormat.addInputPath(job, new Path(argArray[0 ]));
FileOutputFormat.setOutputPath(job,new Path(argArray[1 ]));
System.exit(job.waitForCompletion(true )?0 :1 );
}
}
"
数据去重 "主要是为了掌握和利用
并行化思想 来对数据进行
有意义 的
筛选 。
统计大数据集上的数据种类个数 、
从网站日志中计算访问地 等这些看似庞杂的任务都会涉及数据去重。下面就进入这个实例的MapReduce程序设计。
实例描述
对数据文件中的数据进行去重。数据文件中的每行都是一个数据。
样例输入 如下所示:
1)file1:
2012-3-1 a
2012-3-2 b
2012-3-3 c
2012-3-4 d
2012-3-5 a
2012-3-6 b
2012-3-7 c
2012-3-3 c
2)file2:
2012-3-1 b
2012-3-2 a
2012-3-3 b
2012-3-4 d
2012-3-5 a
2012-3-6 c
2012-3-7 d
2012-3-3 c
样例输出 如下所示:
2012-3-1 a
2012-3-1 b
2012-3-2 a
2012-3-2 b
2012-3-3 b
2012-3-3 c
2012-3-4 d
2012-3-5 a
2012-3-6 b
2012-3-6 c
2012-3-7 c
2012-3-7 d
设计思路
数据去重 的最终目标 是让原始数据 中出现次数超过一次 的数据 在输出文件 中只出现一次 。 我们自然而然会想到将同一个数据的所有记录都交给一台 reduce 机器,无论这个数据出现多少次,只要在最终结果中输出一次就可以了。具体就是reduce的输入 应该以数据 作为key ,而对value-list则没有 要求。当reduce接收到一个时就直接 将key复制到输出的key中,并将value设置成空值 。
在MapReduce流程中,map的输出经过shuffle过程聚集成后会交给reduce。所以从设计好的reduce输入可以反推出map的输出key应为数据,value任意。继续反推,map输出数 据的key为数据,而在这个实例中每个数据代表输入文件中的一行内容,所以map阶段要完成的任务就是在采用Hadoop默认的作业输入方式之后,将 value设置为key,并直接输出(输出中的value任意)。map中的结果经过shuffle过程之后交给reduce。reduce阶段不会管每 个key有多少个value,它直接将输入的key复制为输出的key,并输出就可以了(输出中的value被设置成空了)。
3. 排序
"数据排序 "是许多实际任务执行时要完成的第一项工作,比如学生成绩评比 、数据建立索引 等。这个实例和数据去重类似,都是先 对原始数据 进行初步处理 ,为进一步 的数据操作打好基础 。下面进入这个示例。
实例描述
对输入文件中数据进行排序。输入文件 中的每行内容 均为一个数字 ,即一个数据 。要求在输出 中每行有两个间隔 的数字,其中,第一个 代表原始数据在原始数据集中的位次 ,第二 个代表原始数据 。
样例输入 :
1)file1:
2
32
654
32
15
756
65223
2)file2:
5956
22
650
92
3)file3:
26
54
6
样例输出 :
1 2
2 6
3 15
4 22
5 26
6 32
7 32
8 54
9 92
10 650
11 654
12 756
13 5956
14 65223
设计思路
这个实例仅仅 要求对输入数据进行排序 ,熟悉MapReduce过程的读者会很快想到在MapReduce过程中就有排序,是否可以利用这个默认 的排序,而不需要自己再实现具体的排序呢?答案是肯定的。
但是在使用之前首先 需要了解 它的默认排序规则 。它是按照key 值进行排序 的,如果key为封装int的IntWritable 类型,那么MapReduce按照数字大小 对key排序,如果key为封装为String的Text 类型,那么MapReduce按照字典顺序 对字符串排序。
了解了这个细节,我们就知道应该使用封装int的IntWritable型数据结构了。也就是在map中将读入的数据转化成 IntWritable型,然后作为key值输出(value任意)。reduce拿到之后,将输入的 key作为value输出,并根据value-list 中元素 的个数 决定输出的次数。输出的key(即代码中的linenum)是一个全局变量,它统计当前key的位次。需要注意的是这个程序中没有配置 Combiner,也就是在MapReduce过程中不使用Combiner。这主要是因为使用map和reduce就已经能够完成任务了。
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
//Administrator
public class SortExample {
private static class SortMapper extends Mapper{
@Override
protected void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
context.write(new IntWritable(Integer.parseInt(value.toString().trim())), new IntWritable(0));
}
}
private static class SortReducer extends Reducer{
private int index=0;
@Override
protected void reduce(IntWritable key, Iterable values,
Context context)
throws IOException, InterruptedException {
for(IntWritable i:values){
index++;
context.write(new Text(index+""),new Text(key.get()+""));
}
}
}
/**
* @param args
*/
public static void main(String[] args) throws Exception{
Configuration conf=new Configuration();
String[]argArray=new GenericOptionsParser(conf, args).getRemainingArgs();
if(argArray.length!=2){
System.out.println("请输入两个参数");
System.exit(1);
}
Job job=Job.getInstance(conf,"sort");
job.setJarByClass(SortExample.class);
job.setMapperClass(SortMapper.class);
job.setMapOutputKeyClass(IntWritable.class);
job.setMapOutputValueClass(IntWritable.class);
job.setReducerClass(SortReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
FileInputFormat.addInputPath(job, new Path(argArray[0]));
FileOutputFormat.setOutputPath(job, new Path(argArray[1]));
System.exit(job.waitForCompletion(true)?0:1);
}
}
4. 单表连接
[java] view plaincopy
package demo;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
import org.apache.hadoop.vod.Ejob;
public class SelfJoin {
private static class SelfJoinMapper extends Mapper{
@Override
protected void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
String str=value.toString();
String[] nameArray=str.split(" " );
context.write(new Text(nameArray[1 ]), new Text("1-" +nameArray[0 ]+"-" +nameArray[1 ]));
context.write(new Text(nameArray[0 ]), new Text("2-" +nameArray[0 ]+"-" +nameArray[1 ]));
}
}
private static class SelfJoinReducer extends Reducer{
@Override
protected void reduce(Text key, Iterable values,
Context context)
throws IOException, InterruptedException {
List outKey=new ArrayList();
List outValue=new ArrayList();
for (Text value:values){
String[] relationArray=value.toString().split("-" );
if (relationArray[0 ].equals("1" )){
outKey.add(relationArray[1 ]);
}else if (relationArray[0 ].equals("2" )){
outValue.add(relationArray[2 ]);
}
}
for (String k:outKey){
for (int i=0 ;i
context.write(new Text(k), new Text(outValue.get(i)));
}
}
}
}
public static void main(String[] args) throws Exception{
File jarFile = Ejob.createTempJar("bin" );
ClassLoader classLoader = Ejob.getClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
Configuration conf=new Configuration();
String [] argArray=new GenericOptionsParser(conf, args).getRemainingArgs();
if (argArray.length!=2 ){
System.out.println("参数错误" );
System.exit(1 );
}
JobConf jobConf=new JobConf(conf);
jobConf.setJar(jarFile.toString());
Job job=Job.getInstance(jobConf,"self join" );
job.setJarByClass(SelfJoin.class );
job.setMapperClass(SelfJoinMapper.class );
job.setMapOutputKeyClass(Text.class );
job.setMapOutputValueClass(Text.class );
job.setReducerClass(SelfJoinReducer.class );
job.setOutputKeyClass(Text.class );
job.setOutputValueClass(Text.class );
FileInputFormat.addInputPath(job, new Path(argArray[0 ]));
FileOutputFormat.setOutputPath(job, new Path(argArray[1 ]));
System.exit(job.waitForCompletion(true )?0 :1 );
}
}
单表关联
前面的实例都是在数据上进行一些简单的处理,为进一步的操作打基础。"单表关联 "这个实例要求 从给出的数据 中寻找 所关心的数据 ,它是对原始数据 所包含信息的挖掘 。下面进入这个实例。
实例描述
实例中给出child-parent (孩子——父母)表,要求输出grandchild-grandparent (孙子——爷奶)表。
样例输入 如下所示。
file:
child parent
Tom Lucy
Tom Jack
Jone Lucy
Jone Jack
Lucy Mary
Lucy Ben
Jack Alice
Jack Jesse
Terry Alice
Terry Jesse
Philip Terry
Philip Alma
Mark Terry
Mark Alma
样例输出如下所示。
file:
grandchild grandparent
Tom Alice
Tom Jesse
Jone Alice
Jone Jesse
Tom Mary
Tom Ben
Jone Mary
Jone Ben
Philip Alice
Philip Jesse
Mark Alice
Mark Jesse
前面的实例都是在数据上进行一些简单的处理,为进一步的操作打基础。"单表关联 "这个实例要求 从给出的数据 中寻找 所关心的数据 ,它是对原始数据 所包含信息的挖掘 。下面进入这个实例。
实例描述
实例中给出child-parent (孩子——父母)表,要求输出grandchild-grandparent (孙子——爷奶)表。
设计思路
分析这个实例,显然需要进行单表连接,连接的是左表 的parent 列和右表 的child 列,且左表 和右表 是同一个表 。
连接结果 中除去 连接的两列就是所需要的结果——"grandchild--grandparent"表。要用MapReduce解决这个实例,首先 应该考虑如何实现表 的自连接 ;其次 就是连接列 的设置 ;最后 是结果 的整理 。
考虑到MapReduce的shuffle过程会将相同的key会连接在一起,所以可以将map结果的key 设置成待连接 的列 ,然后列中相同的值就自然会连接在一起了。再与最开始的分析联系起来:
要连接的是左表的parent列和右表的child列,且左表和右表是同一个表,所以在map阶段 将读入数据分割 成child 和parent 之后,会将parent 设置成key ,child 设置成value 进行输出,并作为左表 ; 再将同一对child 和parent 中的child 设置成key ,parent 设置成value 进行输出,作为右表 。为了区分 输出中的左右表 ,需要在输出的value 中再 加上左右表 的信息 ,比如在value的String最开始处加上字符1 表示左表 ,加上字符2 表示右表 。这样在map的结果中就形成了左表和右表,然后在shuffle过程中完成连接。reduce接收到连接的结果,其中每个key的value-list就包含了"grandchild--grandparent"关系。取出每个key的value-list进行解析,将左表 中的child 放入一个数组 ,右表 中的parent 放入一个数组 ,然后对两个数组求笛卡尔积 就是最后的结果了。
自连接运行详解
(1)Map处理:
map函数输出结果如下所示。
child parent àà 忽略此行
Tom Lucy àà
Tom Jack àà
Jone Lucy àà
Jone Jack àà
Lucy Mary àà
Lucy Ben àà
Jack Alice àà
Jack Jesse àà
Terry Alice àà
Terry Jesse àà
Philip Terry àà
Philip Alma àà
Mark Terry àà
Mark Alma àà
(2)Shuffle处理
在shuffle过程中完成连接。
map 函数输出
排序结果
shuffle 连接
, 1+Tom+Lucy>
, 2+Tom+Lucy>
, 1+Tom+Jack>
, 2+Tom+Jack>
, 1+Jone+Lucy>
, 2+Jone+Lucy>
, 1+Jone+Jack>
, 2+Jone+Jack>
, 1+Lucy+Mary>
, 2+Lucy+Mary>
, 1+Lucy+Ben>
, 2+Lucy+Ben>
, 1+Jack+Alice>
, 2+Jack+Alice>
, 1+Jack+Jesse>
, 2+Jack+Jesse>
, 1+Terry+Alice>
, 2+Terry+Alice>
, 1+Terry+Jesse>
, 2+Terry+Jesse>
, 1+Philip+Terry>
, 2+Philip+Terry>
, 1+Philip+Alma>
, 2+Philip+Alma>
, 1+Mark+Terry>
, 2+Mark+Terry>
, 1+Mark+Alma>
, 2+Mark+Alma>
, 1+Jack+Alice>
, 1+Terry+Alice>
, 1+Philip+Alma>
, 1+Mark+Alma>
, 1+Lucy+Ben>
, 1+Tom+Jack>
, 1+Jone+Jack>
, 2+Jack+Alice>
, 2+Jack+Jesse>
, 1+Jack+Jesse>
, 1+Terry+Jesse>
, 2+Jone+Lucy>
, 2+Jone+Jack>
, 1+Tom+Lucy>
, 1+Jone+Lucy>
, 2+Lucy+Mary>
, 2+Lucy+Ben>
, 1+Lucy+Mary>
, 2+Mark+Terry>
, 2+Mark+Alma>
, 2+Philip+Terry>
, 2+Philip+Alma>
, 2+Terry+Alice>
, 2+Terry+Jesse>
, 1+Philip+Terry>
, 1+Mark+Terry>
, 2+Tom+Lucy>
, 2+Tom+Jack>
, 1+Jack+Alice ,
1+Terry+Alice ,
1+Philip+Alma ,
1+Mark+Alma >
, 1+Lucy+Ben>
, 1+Tom+Jack ,
1+Jone+Jack ,
2+Jack+Alice ,
2+Jack+Jesse >
, 1+Jack+Jesse ,
1+Terry+Jesse >
, 2+Jone+Lucy ,
2+Jone+Jack>
, 1+Tom+Lucy ,
1+Jone+Lucy ,
2+Lucy+Mary ,
2+Lucy+Ben>
, 1+Lucy+Mary ,
2+Mark+Terry ,
2+Mark+Alma>
, 2+Philip+Terry ,
2+Philip+Alma>
, 2+Terry+Alice ,
2+Terry+Jesse ,
1+Philip+Terry ,
1+Mark+Terry>
, 2+Tom+Lucy ,
2+Tom+Jack>
(3)Reduce处理
首先 由语句"0 != grandchildnum && 0 != grandparentnum "得知,只要在"value-list"中没有左表或者右表,则不会做处理,可以根据这条规则去除无效 的shuffle连接 。
无效 的 shuffle 连接
有效 的 shuffle 连接
, 1+Jack+Alice ,
1+Terry+Alice ,
1+Philip+Alma ,
1+Mark+Alma >
, 1+Lucy+Ben>
, 1+Jack+Jesse ,
1+Terry+Jesse >
, 2+Jone+Lucy ,
2+Jone+Jack>
, 1+Lucy+Mary ,
2+Mark+Terry ,
2+Mark+Alma>
, 2+Philip+Terry ,
2+Philip+Alma>
, 2+Tom+Lucy ,
2+Tom+Jack>
, 1+Tom+Jack ,
1+Jone+Jack ,
2+Jack+Alice ,
2+Jack+Jesse >
, 1+Tom+Lucy ,
1+Jone+Lucy ,
2+Lucy+Mary ,
2+Lucy+Ben>
, 2+Terry+Alice ,
2+Terry+Jesse ,
1+Philip+Terry ,
1+Mark+Terry>
然后根据下面语句进一步对有效的shuffle连接做处理。
// 左表,取出child放入grandchildren
if ('1' == relationtype) {
grandchild[grandchildnum] = childname;
grandchildnum++;
}
// 右表,取出parent放入grandparent
if ('2' == relationtype) {
grandparent[grandparentnum] = parentname;
grandparentnum++;
}
针对一条数据进行分析:
1+Jone+Jack,
2+Jack+Alice,
2+Jack+Jesse >
分析结果 :左表 用"字符1 "表示,右表 用"字符2 "表示,上面的中的"key "表示左表与右表 的连接键 。而"value-list "表示以"key"连接 的左表与右表 的相关数据 。
根据上面针对左表与右表不同的处理规则,取得两个数组的数据如下所示:
grandchild
Tom 、 Jone ( grandchild[grandchildnum] = childname; )
grandparent
Alice 、 Jesse ( grandparent[grandparentnum] = parentname; )
然后根据下面语句进行处理。
for (int m = 0; m < grandchildnum; m++) {
for (int n = 0; n < grandparentnum; n++) {
context.write(new Text(grandchild[m]), new Text(grandparent[n]));
}
}
处理结果如下面所示:
Tom Jesse
Tom Alice
Jone Jesse
Jone Alice
其他的有效shuffle连接 处理都是如此 。
数据:
Tom Lucy
Tom Jack
Jone Lucy
Jone Jack
Lucy Mary
Lucy Ben
Jack Alice
Jack Jesse
Terry Alice
Terry Jesse
Philip Terry
Philip Alma
Mark Terry
Mark Alma
结果:
Tom Alice
Tom Jesse
Jone Alice
Jone Jesse
Tom Mary
Tom Ben
Jone Mary
Jone Ben
Philip Alice
Philip Jesse
Mark Alice
Mark Jesse
5. 多表连接
package demo;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
import org.apache.hadoop.vod.Ejob;
public class MultiTableJoin {
private static class MultiTableMapper extends Mapper{
@Override
protected void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
String str=value.toString();
if (str.charAt(0 )>'0' &&str.charAt(0 )<'9' ){
context.write(new Text(str.charAt(0 )+"" ), new Text("2-" +str.substring(1 ).trim()));
}else {
context.write(new Text(str.substring(str.length()-1 )), new Text("1-" +str.substring(0 , str.length()-1 ).trim()));
}
}
}
private static class MultiTableReducer extends Reducer{
@Override
protected void reduce(Text key, Iterable values,
Context context)
throws IOException, InterruptedException {
ListkeyList=new ArrayList();
ListvalueList=new ArrayList();
for (Text value:values){
String str=value.toString();
String []strArray=str.split("-" );
if (strArray[0 ].equals("1" )){
keyList.add(strArray[1 ]);
}else if (strArray[0 ].equals("2" )){
valueList.add(strArray[1 ]);
}
}
for (String skey:keyList){
for (String svalue:valueList){
context.write(new Text(skey), new Text(svalue));
}
}
}
}
public static void main(String[] args) throws Exception{
File jarFile=Ejob.createTempJar("bin" );
ClassLoader classLoader=Ejob.getClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
Configuration conf=new Configuration();
String [] argArray=new GenericOptionsParser(conf, args).getRemainingArgs();
if (argArray.length!=2 ){
System.out.println("参数错误" );
System.exit(1 );
}
JobConf jobConf=new JobConf(conf);
jobConf.setJar(jarFile.toString());
Job job=Job.getInstance(jobConf,"multiTalbe join" );
job.setMapperClass(MultiTableMapper.class );
job.setMapOutputKeyClass(Text.class );
job.setMapOutputValueClass(Text.class );
job.setReducerClass(MultiTableReducer.class );
job.setOutputKeyClass(Text.class );
job.setOutputValueClass(Text.class );
FileInputFormat.addInputPath(job, new Path(argArray[0 ]));
FileOutputFormat.setOutputPath(job, new Path(argArray[1 ]));
System.exit(job.waitForCompletion(true )?0 :1 );
}
}
多表关联
多表 关联和单表 关联类似 ,它也是通过对原始数据进行一定的处理,从其中挖掘出关心的信息。下面进入这个实例。
实例描述
输入是两个文件,一个代表工厂表 ,包含工厂名 列和地址编号 列;另一个代表地址表 ,包含地址名 列和地址编号 列。要求从输入数据 中找出工厂名 和地址名 的对应关系 ,输出"工厂名——地址名 "表。
样例输入 如下所示。
1)factory:
factoryname addressed
Beijing Red Star 1
Shenzhen Thunder 3
Guangzhou Honda 2
Beijing Rising 1
Guangzhou Development Bank 2
Tencent 3
Back of Beijing 1
2)address:
addressID addressname
1 Beijing
2 Guangzhou
3 Shenzhen
4 Xian
样例输出 如下所示。
factoryname addressname
Back of Beijing Beijing
Beijing Red Star Beijing
Beijing Rising Beijing
Guangzhou Development Bank Guangzhou
Guangzhou Honda Guangzhou
Shenzhen Thunder Shenzhen
Tencent Shenzhen
设计思路
多表关联和单表关联相似,都类似于数据库 中的自然连接 。相比单表关联,多表关联的左右表和连接列更加清楚。所以可以采用和单表 关联的相同 的处理方式 ,map识别出输入的行属于哪个表之后,对其进行分割,将连接的列值 保存在key 中,另一列 和左右表标识 保存在value 中,然后输出。reduce拿到连接结果之后,解析value内容,根据标志将左右表内容分开存放,然后求笛卡尔积 ,最后直接输出。
6. 平均成绩
"平均成绩"主要目的还是在重温经典 "WordCount"例子,可以说是在基础上的微变化 版,该实例主要就是实现一个计算学生平均成绩的例子。
实例描述
对输入文件中数据进行就算学生平均成绩。输入文件中的每行内容 均为一个学生 的姓名 和他相应的成绩 ,如果有多门学科,则每门学科为一个文件。要求在输出中每行有两个间隔的数据,其中,第一个 代表学生的姓名 ,第二个 代表其平均成绩 。
样本输入 :
1)math:
张三 88
李四 99
王五 66
赵六 77
2)china:
张三 78
李四 89
王五 96
赵六 67
3)english:
张三 80
李四 82
王五 84
赵六 86
样本输出 :
张三 82
李四 90
王五 82
赵六 76
设计思路
计算学生平均成绩是一个仿"WordCount"例子,用来重温一下开发MapReduce程序的流程。程序包括两部分的内容:Map部分和Reduce部分,分别实现了map和reduce的功能。
Map处理 的是一个纯文本文件,文件中存放的数据时每一行表示一个学生的姓名和他相应一科成绩。Mapper处理的数据是由InputFormat分解过的数据集,其中InputFormat的作用是将数据集切割成小数据集InputSplit,每一个InputSlit将由一个Mapper负责处理。此外,InputFormat中还提供了一个RecordReader的实现,并将一个InputSplit解析成对提供给了map函数。InputFormat的默认值是TextInputFormat,它针对文本文件,按行将文本切割成InputSlit,并用LineRecordReader将InputSplit解析成对,key是行在文本中的位置,value是文件中的一行。
Map的结果会通过partion分发到Reducer,Reducer做完Reduce操作后,将通过以格式OutputFormat输出。
Mapper最终处理的结果对,会送到Reducer中进行合并,合并的时候,有相同key的键/值对则送到同一个Reducer上。Reducer是所有用户定制Reducer类地基础,它的输入是key和这个key对应的所有value的一个迭代器,同时还有Reducer的上下文。Reduce的结果由Reducer.Context的write方法输出到文件中。
代码输出:
import java.io.IOException; import java.util.Iterator; import java.util.StringTokenizer; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.input.TextInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat; import org.apache.hadoop.util.GenericOptionsParser; public class Score { public static class Map extends Mapper { // 实现map函数 public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 将输入的纯文本文件的数据转化成String String line = value.toString(); // 将输入的数据首先按行进行分割 StringTokenizer tokenizerArticle = new StringTokenizer(line, "\n"); // 分别对每一行进行处理 while (tokenizerArticle.hasMoreElements()) { // 每行按空格划分 StringTokenizer tokenizerLine = new StringTokenizer(tokenizerArticle.nextToken()); String strName = tokenizerLine.nextToken();// 学生姓名部分 String strScore = tokenizerLine.nextToken();// 成绩部分 Text name = new Text(strName); int scoreInt = Integer.parseInt(strScore); // 输出姓名和成绩 context.write(name, new IntWritable(scoreInt)); } } } public static class Reduce extends Reducer { // 实现reduce函数 public void reduce(Text key, Iterable values, Context context) throws IOException, InterruptedException { int sum = 0; int count = 0; Iterator iterator = values.iterator(); while (iterator.hasNext()) { sum += iterator.next().get();// 计算总分 count++;// 统计总的科目数 } int average = (int) sum / count;// 计算平均成绩 context.write(key, new IntWritable(average)); } } public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); // String[] ioArgs = new String[] { "score_in", "score_out" }; String[] otherArgs = new GenericOptionsParser(conf, args1).getRemainingArgs(); if (otherArgs.length != 2) { System.err.println("Usage: Score Average "); System.exit(2); } Job job =Job.getInstance(conf, "Score Average"); job.setJarByClass(Score.class); // 设置Map、Combine和Reduce处理类 job.setMapperClass(Map.class); job.setCombinerClass(Reduce.class); job.setReducerClass(Reduce.class); // 设置输出类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); // 将输入的数据集分割成小数据块splites,提供一个RecordReder的实现 job.setInputFormatClass(TextInputFormat.class); // 提供一个RecordWriter的实现,负责数据输出 job.setOutputFormatClass(TextOutputFormat.class); // 设置输入和输出目录 FileInputFormat.addInputPath(job, new Path(otherArgs[0])); FileOutputFormat.setOutputPath(job, new Path(otherArgs[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); } }
7. 倒排索引 "
倒排索引 "是
文档检索系统 中
最常用 的
数据结构 ,被广泛地应用于
全文搜索引擎 。它
主要 是用来
存储 某个
单词(或词组)在 一个文档或一组文档 中的
存储位置 的
映射 ,即提供了一种
根据内容来查找文档 的
方式 。由于不是根据文档来确定文档所包含的内容,而是进行相反的操作,因而称为倒排索引(Inverted Index)。
实例描述
通常情况下,倒排索引由一个单词(或词组)以及相关的文档列表组成,文档列表中的文档或者是标识文档的ID号,或者是指文档所在位置的URL,如下图所示。
倒排索引结构
从图中可以看出,单词1出现在{文档1,文档4,文档13,……}中,单词2出现在{文档3,文档5,文档15,……}中,而单词3出现在{文档1,文档8,文档20,……}中。在实际应用 中,还需要 给每个文档 添加一个权值 ,用来指出 每个文档与搜索内容的相关度 ,如下图所示。
添加权重的倒排索引
最常用的是使用词频 作为权重 ,即记录单词在文档中出现的次数。以英文为例,如下图所示,索引文件中的"MapReduce"一行表示:"MapReduce"这个单词在文本T0中出现过1次,T1中出现过1次,T2中出现过2次。当搜索条件为"MapReduce"、"is"、"Simple"时,对应的集合为:{T0,T1,T2}∩{T0,T1}∩{T0,T1}={T0,T1},即文档T0和T1包含了所要索引的单词,而且只有T0是连续的。
倒排索引示例
更复杂的权重还可能要记录单词在多少个文档中出现过,以实现TF-IDF(Term Frequency-Inverse Document Frequency)算法,或者考虑单词在文档中的位置信息(单词是否出现在标题中,反映了单词在文档中的重要性)等。
样例输入 如下所示。
1)file1:
MapReduce is simple
2)file2:
MapReduce is powerful is simple
3)file3:
Hello MapReduce bye MapReduce
样例输出 如下所示。
MapReduce file1.txt:1;file2.txt:1;file3.txt:2;
is file1.txt:1;file2.txt:2;
simple file1.txt:1;file2.txt:1;
powerful file2.txt:1;
Hello file3.txt:1;
bye file3.txt:1;
设计思路
实现"倒排索引 "只要关注的信息为:单词 、文档URL 及词频 ,如图中所示。但是在实现过程中,索引文件的格式与倒排索引示例有所不同,以避免重写OutPutFormat类。下面根据MapReduce的处理过程 给出倒排索引 的设计思路 。
1)Map过程
首先使用默认的TextInputFormat 类对输入文件 进行处理,得到文本中每行 的偏移量 及其内容 。显然,Map过程首先必须分析输入的对,得到倒排索引中需要的三个信息:单词、文档URL和词频,如下图所示。
Map过程输入/输出
这里存在 两个问题 :第一 ,对只能有两个值,在不使用Hadoop自定义数据类型的情况下,需要根据情况将其中两个值合并 成一个值,作为key或value值;第二 ,通过一个Reduce 过程无法同时完成词频统计 和生成文档列表 ,所以必须增加一个Combine 过程完成词频统计 。
这里讲单词和URL组成key值(如"MapReduce:file1.txt"),将词频作为value,这样做的好处是可以利用MapReduce框架自带的Map端排序,将同一文档 的相同单词 的词频 组成列表 ,传递给Combine 过程,实现类似于WordCount的功能。
2)Combine过程
经过map方法处理后,Combine过程将key值相同的value值累加,得到一个单词在文档在文档中的词频,如下图所示。如果 直接将 Combine过程输入/输出图的输出作为Reduce过程的输入,在Shuffle过程时将面临一个问题 :所有具有相同单词 的记录(由单词、URL和词频组成)应该交由同一个Reducer处理,但当前的key值 无法保证这一点,所以必须修改 key值和value值。这次将单词 作为key 值,URL和词频组 成value 值(如"file1.txt:1")。这样做的好处是可以利用MapReduce框架默认的HashPartitioner类完成Shuffle过程,将相同单词 的所有记录 发送给同一个Reducer 进行处理 。
Combine过程输入/输出
3)Reduce过程
经过上述两个过程后,Reduce过程只需将相同key值的value值组合成倒排索引文件所需的格式即可,剩下的事情就可以直接交给MapReduce框架进行处理了。如Reduce过程输入/输出图所示。索引文件的内容除分隔符外与倒排索引示例图解释相同。
4)需要解决的问题
本实例设计的倒排索引在文件数目 上没有限制 ,但是单词文件不宜过大 (具体值与默认 HDFS块大小及相关配置有关),要保证每个文件对应一个split 。否则,由于Reduce 过程没有进一步统计词频 ,最终结 果可能 会出现词频未统计完全 的单词 。可以通过重写 InputFormat类将每个文件为一个split,避免上述情况。或者执行两次MapReduce ,第一次 MapReduce用于统计词频 ,第二次 MapReduce用于生成倒排索引 。除此之外,还可以利用复合键值对等实现包含更多信息的倒排索引。
Reduce过程输入/输出
程序代码
import java.io.IOException;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class InvertedIndex {
public static class Map extends Mapper
{
private Text keyInfo = new Text(); // 存储单词和URL组合
private Text valueInfo = new Text(); // 存储词频
private FileSplit split; // 存储Split对象
// 实现map函数
public void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
// 获得对所属的FileSplit对象
split = (FileSplit) context.getInputSplit();
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
// key值由单词和URL组成,如"MapReduce:file1.txt"
// 获取文件的完整路径
// keyInfo.set(itr.nextToken()+":"+split.getPath().toString());
// 这里为了好看,只获取文件的名称。
int splitIndex = split.getPath().toString().indexOf("file");
keyInfo.set(itr.nextToken() + ":"
+ split.getPath().toString().substring(splitIndex));
// 词频初始化为1
valueInfo.set("1");
System.out.println(keyInfo);
System.out.println(valueInfo);
context.write(keyInfo, valueInfo);
}
}
}
public static class Combine extends Reducer {
private Text info = new Text();
// 实现reduce函数
public void reduce(Text key, Iterable values, Context context)
throws IOException, InterruptedException {
// 统计词频
int sum = 0;
for (Text value : values) {
sum += Integer.parseInt(value.toString());
}
int splitIndex = key.toString().indexOf(":");
// 重新设置value值由URL和词频组成
info.set(key.toString().substring(splitIndex + 1) + ":" + sum);
// 重新设置key值为单词
key.set(key.toString().substring(0, splitIndex));
context.write(key, info);
}
}
public static class Reduce extends Reducer {
private Text result = new Text();
// 实现reduce函数
public void reduce(Text key, Iterable values, Context context)
throws IOException, InterruptedException {
// 生成文档列表
String fileList = new String();
for (Text value : values) {
fileList += value.toString() + ";";
}
result.set(fileList);
context.write(key, result);
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
String[] args1=args0;
// String[] ioArgs = new String[] { "index_in", "index_out" };
String[] otherArgs = new GenericOptionsParser(conf, args1)
.getRemainingArgs();
if (otherArgs.length != 2) {
System.err.println("Usage: Inverted Index ");
System.exit(2);
}
Job job = Job.getInstance(conf, "Inverted Index");
job.setJarByClass(InvertedIndex.class);
// 设置Map、Combine和Reduce处理类
job.setMapperClass(Map.class);
job.setCombinerClass(Combine.class);
job.setReducerClass(Reduce.class);
// 设置Map输出类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
// 设置Reduce输出类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
// 设置输入和输出目录
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}