編程環境:
Ubuntu16.4 uklin
Hadoop3.2.0
openjdk version "1.8.0_191"
完整代碼已經更新至GitHub,歡迎fork~GitHub鏈接
聲明:創作不易,未經授權不得復制轉載
statement:No reprinting without authorization
二、在本地編寫程序和調試
1、mapper設計:
輸入:
<string line> ---------讀入文檔的每行字符串
處理過程1:
<進行token,正則化去除英文數字外的字符,轉為小寫,利用空格符分詞> ----------得到一個個獨立的英文單詞
處理過程2:
<得到文檔的文件名,加在每隔單詞的后面結合成輸出的key值(中間用特殊字符分隔),這樣設計方便統計每個單詞在每篇文檔中的詞頻信息>
處理過程3:
<將每個key對應的value值設為“1”>
輸出:
<<key1,1>,<key2,1>,<key3,1>...>
示例:
//倒排索引mapper類
public static class InvertedIndexMapper extends Mapper<LongWritable, Text, Text, Text>{
private static Text keyInfo = new Text();// 存儲單詞和文檔名組合 eg: hello
private static final Text valueInfo = new Text("1");// 存儲詞頻,初始化為1
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String line = value.toString();
//去除標點token操作
line = line.replaceAll("[^a-zA-Z0-9]", " ");
line = line.replaceAll("\\s{2,}", " ").trim();
line = line.toLowerCase();
String[] fields = line.split(" ");// 得到字段數組
FileSplit fileSplit = (FileSplit) context.getInputSplit();// 得到這行數據所在的文件切片
String fileName = fileSplit.getPath().getName();// 根據文件切片得到文件名
for (String field : fields) {
if(field!=null){
// key值由單詞和URL組成,如“MapReduce:file1”
keyInfo.set(field + "," + fileName);
context.write(keyInfo, valueInfo);
}
}
}
}
2、Combine設計
通過一個Reduce過程無法同時完成詞頻統計和生成文檔列表,所以必須增加一個Combine過程完成詞頻統計
輸入:
<key,valuelist<1...>> -----eg:<word+’,’+filename, <1,1,1>>
處理過程:
<根據特殊的字符將key進行拆分,將key設置為單詞,并統計詞頻信息,將value list中的每個1相加,將文檔名和詞頻信息組合成新的value輸出,同樣用特殊的字符分隔>
輸出:
<newKey,newValue> --------eg:<word, filename+’,’+countNumber>
//倒排索引combiner類
public static class InvertedIndexCombiner extends Reducer<Text, Text, Text, Text>{
private static Text info = new Text();
// 輸入: <MapReduce:file3 {1,1,...}>
// 輸出:<MapReduce file3:2>
@Override //偽代碼,表示重寫 系統可以幫你檢查正確性
protected void reduce(Text key, Iterable<Text> 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);
}
}
key-value經過map和combine后的變化示例:
3、停用詞處理設計
設計字符串使用config.set()進行傳遞,在程序map-reduce工作前設計方法,得到停用詞列表存儲入string sword中,而后在reducer中重載setup函數,config.get()函數取出停用詞:
public static String catStopWords(Configuration conf, String remoteFilePath) {
Path remotePath = new Path(remoteFilePath);
//String Swords[] = new String[100];
//ArrayList<String> strArray = new ArrayList<String> ();
String sword = "";
try (FileSystem fs = FileSystem.get(conf);
FSDataInputStream in = fs.open(remotePath);
BufferedReader d = new BufferedReader(new InputStreamReader(in));) {
String line;
while ((line = d.readLine()) != null) {
line = line.replaceAll("[^a-zA-Z0-9]", "");
if(line!=null)
sword+=line+",";
//strArray.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return sword;
}
4、reducer設計:
經過combiner之后,Reduce過程只需將相同key值的value值組合成倒排索引文件所需的格式即可,利用value中的詞頻信息,分割相加得到total的詞頻數,注意對于傳入的key值,如果在停用詞列表中出現,則不將其輸出寫入context中,剩下的事情就可以直接交給MapReduce框架進行處理了。
示例輸出:
//倒排索引reducer類
public static class InvertedIndexReducer extends Reducer<Text, Text, Text, Text>{
private static Text result = new Text();
private static String[] fields;
@Override
protected void setup(Context context)
throws IOException, InterruptedException {
try {
//從全局配置獲取配置參數
Configuration conf = context.getConfiguration();
String Str = conf.get("swords"); //這樣就拿到了
fields = Str.split(",");// 得到字段數組
} catch (Exception e) {
e.printStackTrace();
}
}
// 輸入:<MapReduce file3,2>
// 輸出:<MapReduce file1,1;file2,1;file3,2;>
@Override
protected void reduce(Text key, Iterable<Text> values, Context context)
throws IOException, InterruptedException {
// 生成文檔列表
String fileList = new String();
int totalNum = 0;
for (Text value : values) {
String va = value.toString();
int index = va.indexOf(',');
String subva = va.substring(index+1);
int num = Integer.valueOf(subva);
totalNum += num;
fileList += "<" + va + ">;";
}
fileList += "<total," + String.valueOf(totalNum)+">.";
result.set(fileList);
//去除停用詞
String k = key.toString();
k = k.replaceAll("[^a-z0-9]", "");
if(k!=null){
boolean tag = true;
for(String tmp:fields){
//System.out.println(tmp);
if(tmp.equals(k)){
tag = false;
break;
}
}
if(tag){
context.write(key, result);
}
}
}
}