目的
我們一開始有兩個數據,一個是學生表
另一個是選課表
注:實際情況中學生表是一個比較小的表,二選課表是大表
我們通過mapreduce程序實現將選課表中的學號換成姓名。得到新的數據
mapjoin
目錄結構
這一次並不需要用到reduce階段,但是在reducejoin中會使用。我們先看相對簡單的mapjoin。
先看代碼
mapClass
package mapJoin;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: Braylon
* @Date: 2020/1/26 14:38
* @Version: 1.0
*/
public class mapClass extends Mapper<LongWritable, Text, Text, NullWritable> {
Map<String, String> globalMap = new HashMap<>();
//setup方法在被Mapreduce框架只執行一次,在執行map任務之前,進行相關變量或者資源的初始化工作。
@Override
protected void setup(Mapper<LongWritable, Text, Text, NullWritable>.Context context) throws IOException, InterruptedException {
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("D:\\idea\\HDFS\\src\\main\\java\\mapJoin\\stuInfo.txt")), "UTF-8"));
String tmp;
while (StringUtils.isNotEmpty(tmp = reader.readLine())) {
String[] arr = tmp.split(" ");
globalMap.put(arr[0], arr[1]);
}
reader.close();
}
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
Text k = new Text();
String line = value.toString();
String[] split = line.split(" ");
String id = split[0];
String sid = split[1];
String lesson = split[2];
String name = globalMap.get(sid);
String valueOut = id + "\001" + name + "\001" + lesson;
k.set(valueOut);
context.write(k, NullWritable.get());
}
}
這裏重要的知識點其實只有兩個,其他的大家可以參考我原來的blog。
- 首先是map階段的輸入和輸出的數據類型,也就是r<LongWritable, Text, Text, NullWritable>
前兩個不必多說,分別是遊標(行號)和讀取每一行的字符串,後兩個是map階段的輸出類型,也就是我們輸出的就是最終的結果valueOut = id + “\001” + name + “\001” + lesson(是個字符串),然後最後一個不需要,所以就用NullWritable。
- 重寫了setup方法,這個方法,setup方法在被Mapreduce框架只執行一次,在執行map任務之前,進行相關變量或者資源的初始化工作。
簡單來說就是,這次的邏輯需要用到兩個文件,所以需要先讀取學生 表的文件信息,再在map階段進行替換生成新的數據組合。
driver類
package mapJoin;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
/**
* @Author: Braylon
* @Date: 2020/1/26 14:57
* @Version: 1.0
*/
public class driver {
public static void main(String[] args) throws IOException, URISyntaxException, ClassNotFoundException, InterruptedException {
args = new String[]{"lessonInfo.txt", "out"};
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
job.setJarByClass(driver.class);
job.setMapperClass(mapClass.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
job.addCacheFile(new URI("file:////D:/idea/HDFS/src/main/java/mapJoin/stuInfo.txt"));
job.setNumReduceTasks(0);//不需要reduce
job.waitForCompletion(true);
}
}
這個需要說的就是一個:
- job.addCacheFile(new URI(“file:////stuInfo.txt”));
這個很重要,字面理解就是將這個文件增加到了緩存,主要原因就是,這是一個小文件,在多次的讀取的情況下會大大增加程序的效率。這在實際應用中也是一個比較重要的技巧。
reduceJoin
上一個是比較簡單的join程序,並且沒有用到reduce階段
下面我們同樣解決上述問題,但是我們發現在實際的應用中往往是很多的文件進行處理和信息的讀取組合,怎麼可能都在setup階段呢,所以我們有更有針對性的處理方法。
目錄結構
代碼:
databean類
package reduceJoin;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* @Author: Braylon
* @Date: 2020/1/26 15:52
* @Version: 1.0
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class dataBean implements Writable {
private String sid;
private String name;
private String lid;
private String lName;
private Integer flag;
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeUTF(sid);
dataOutput.writeUTF(name);
dataOutput.writeUTF(lid);
dataOutput.writeUTF(lName);
dataOutput.writeInt(flag);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
this.sid = dataInput.readUTF();
this.name = dataInput.readUTF();
this.lid = dataInput.readUTF();
this.lName = dataInput.readUTF();
this.flag = dataInput.readInt();
}
@Override
public String toString() {
return lid + "\001" + name + "\001" + lName + "\n";
}
}
序列化的知識點大家可以看前面的blog
這裏其實沒什麼可說的
map類
package reduceJoin;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import java.io.IOException;
/**
* @Author: Braylon
* @Date: 2020/1/26 15:51
* @Version: 1.0
*/
public class mapClass extends Mapper<LongWritable, Text, Text, dataBean> {
Text k = new Text();
dataBean bean = new dataBean();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
FileSplit filesplit = (FileSplit) context.getInputSplit();
String fName = filesplit.getPath().getName();
if (fName.startsWith("stuInfo")) {
String[] field = value.toString().split(" ");
bean.setFlag(0);
bean.setSid(field[0]);
bean.setName(field[1]);
bean.setLid("");
bean.setLName("");
} else if (fName.startsWith("lessonInfo")) {
String[] field = value.toString().split(" ");
bean.setFlag(1);
bean.setSid(field[1]);
bean.setName("");
bean.setLid(field[0]);
bean.setLName(field[2]);
}
k.set(bean.getSid());
context.write(k, bean);
}
}
知識點:
- FileSplit filesplit = (FileSplit) context.getInputSplit();
這樣做的原因就是可以知道讀取的數據隸屬的文件名,這樣就可以通過流程控制進行不同文件的數據處理。
reduce類
package reduceJoin;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
/**
* @Author: Braylon
* @Date: 2020/1/26 16:10
* @Version: 1.0
*/
public class reduceClass extends Reducer<Text, dataBean, dataBean, NullWritable> {
@Override
protected void reduce(Text key, Iterable<dataBean> values, Context context) throws IOException, InterruptedException {
ArrayList<dataBean> dataBeans = new ArrayList<>();
dataBean bean = new dataBean();
for (dataBean b0 : values) {
if (b0.getFlag().equals(0)) {
try {
BeanUtils.copyProperties(bean, b0);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} else if (b0.getFlag().equals(1)) {
dataBean tmpBean = new dataBean();
try {
BeanUtils.copyProperties(tmpBean,b0);
dataBeans.add(tmpBean);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
for (dataBean b0 : dataBeans) {
b0.setName(bean.getName());
context.write(b0,NullWritable.get());
}
}
}
知識點:
- 這裏有一個邏輯問題可能比較繞人,
首先我們定義了一個arraylist來存儲每一個選課表的實體,注意這個list中的每個對象都是缺少name屬性的。然後我們定義了一個dataBean對象,來存儲學生表的信息,但是爲什麼這個就是不是一個list呢,因爲學生表一定是唯一的也就是說,對於唯一的學號一定只有一個dataBean對象有name屬性。
然後我們for循環將每一個選課表對象增加name屬性。
driver類
package reduceJoin;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
/**
* @Author: Braylon
* @Date: 2020/1/26 16:19
* @Version: 1.0
*/
public class driverClass {
public static void main(String[] args) throws IOException {
args = new String[]{"resource", "out"};
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
job.setJarByClass(driverClass.class);
job.setMapperClass(mapClass.class);
job.setReducerClass(reduceClass.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(dataBean.class);
job.setOutputKeyClass(dataBean.class);
job.setOutputValueClass(NullWritable.class);
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
try {
job.waitForCompletion(true);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
注意這裏我寫的args的輸入不再是一個單獨的txt文件而是一個有很多txt文件的文件夾。