Hadoop叢集(第9期)_MapReduce初級案例

dd_dong發表於2013-06-13

來自:http://www.cnblogs.com/xia520pi/archive/2012/06/04/2534533.html

1、資料去重

   "資料去重"主要是為了掌握和利用並行化思想來對資料進行有意義篩選統計大資料集上的資料種類個數從網站日誌中計算訪問地等這些看似龐雜的任務都會涉及資料去重。下面就進入這個例項的MapReduce程式設計。

1.1 例項描述

  對資料檔案中的資料進行去重。資料檔案中的每行都是一個資料。

  樣例輸入如下所示:

     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

 

1.2 設計思路

  資料去重最終目標是讓原始資料出現次數超過一次資料輸出檔案出現一次我們自然而然會想到將同一個資料的所有記錄都交給一臺reduce機器,無論這個資料出現多少次,只要在最終結果中輸出一次就可以了。具體就是reduce的輸入應該以資料作為key,而對value-list則沒有要求。當reduce接收到一個<key,value-list>時就直接將key複製到輸出的key中,並將value設定成空值

  在MapReduce流程中,map的輸出<key,value>經過shuffle過程聚整合<key,value-list>後會交給reduce。所以從設計好的reduce輸入可以反推出map的輸出key應為資料,value任意。繼續反推,map輸出資料的key為資料,而在這個例項中每個資料代表輸入檔案中的一行內容,所以map階段要完成的任務就是在採用Hadoop預設的作業輸入方式之後,將value設定為key,並直接輸出(輸出中的value任意)。map中的結果經過shuffle過程之後交給reduce。reduce階段不會管每個key有多少個value,它直接將輸入的key複製為輸出的key,並輸出就可以了(輸出中的value被設定成空了)。

1.3 程式程式碼

     程式程式碼如下所示:

 

package com.hebut.mr;

 

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 Dedup {

 

    //map將輸入中的value複製到輸出資料的key上,並直接輸出

    public static class Map extends Mapper<Object,Text,Text,Text>{

        private static Text line=new Text();//每行資料

       

        //實現map函式

        public void map(Object key,Text value,Context context)

                throws IOException,InterruptedException{

            line=value;

            context.write(linenew Text(""));

        }

       

    }

   

    //reduce將輸入中的key複製到輸出資料的key上,並直接輸出

    public static class Reduce extends Reducer<Text,Text,Text,Text>{

        //實現reduce函式

        public void reduce(Text key,Iterable<Text> values,Context context)

                throws IOException,InterruptedException{

            context.write(key, new Text(""));

        }

       

    }

   

    public static void main(String[] args) throws Exception{

        Configuration conf = new Configuration();

        //這句話很關鍵

        conf.set("mapred.job.tracker""192.168.1.2:9001");

       

        String[] ioArgs=new String[]{"dedup_in","dedup_out"};

     String[] otherArgs = new GenericOptionsParser(conf, ioArgs).getRemainingArgs();

     if (otherArgs.length != 2) {

     System.err.println("Usage: Data Deduplication <in> <out>");

     System.exit(2);

     }

     

     Job job = new Job(conf, "Data Deduplication");

     job.setJarByClass(Dedup.class);

     

     //設定MapCombineReduce處理類

     job.setMapperClass(Map.class);

     job.setCombinerClass(Reduce.class);

     job.setReducerClass(Reduce.class);

     

     //設定輸出型別

     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);

     }

}

 

1.4 程式碼結果

     1)準備測試資料

     通過Eclipse下面的"DFS Locations"在"/user/hadoop"目錄下建立輸入檔案"dedup_in"資料夾(備註:"dedup_out"不需要建立。)如圖1.4-1所示,已經成功建立。

         

圖1.4-1 建立"dedup_in"                                   圖1.4.2 上傳"file*.txt"

 

     然後在本地建立兩個txt檔案,通過Eclipse上傳到"/user/hadoop/dedup_in"資料夾中,兩個txt檔案的內容如"例項描述"那兩個檔案一樣。如圖1.4-2所示,成功上傳之後。

     從SecureCRT遠處檢視"Master.Hadoop"的也能證實我們上傳的兩個檔案。

 

 

    檢視兩個檔案的內容如圖1.4-3所示:

 

圖1.4-3 檔案"file*.txt"內容

2)檢視執行結果

     這時我們右擊Eclipse的"DFS Locations"中"/user/hadoop"資料夾進行重新整理,這時會發現多出一個"dedup_out"資料夾,且裡面有3個檔案,然後開啟雙其"part-r-00000"檔案,會在Eclipse中間把內容顯示出來。如圖1.4-4所示。

 

圖1.4-4 執行結果

 

    此時,你可以對比一下和我們之前預期的結果是否一致。

2、資料排序

  "資料排序"是許多實際任務執行時要完成的第一項工作,比如學生成績評比資料建立索引等。這個例項和資料去重類似,都是原始資料進行初步處理,為進一步的資料操作打好基礎。下面進入這個示例。

2.1 例項描述

    對輸入檔案中資料進行排序。輸入檔案中的每行內容均為一個數字即一個資料。要求在輸出中每行有兩個間隔的數字,其中,第一個代表原始資料在原始資料集中的位次第二個代表原始資料

    樣例輸入

    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

 

2.2 設計思路

  這個例項僅僅要求對輸入資料進行排序,熟悉MapReduce過程的讀者會很快想到在MapReduce過程中就有排序,是否可以利用這個預設的排序,而不需要自己再實現具體的排序呢?答案是肯定的。

  但是在使用之前首先需要瞭解它的預設排序規則。它是按照key值進行排序的,如果key為封裝int的IntWritable型別,那麼MapReduce按照數字大小對key排序,如果key為封裝為String的Text型別,那麼MapReduce按照字典順序對字串排序。

  瞭解了這個細節,我們就知道應該使用封裝int的IntWritable型資料結構了。也就是在map中將讀入的資料轉化成IntWritable型,然後作為key值輸出(value任意)。reduce拿到<key,value-list>之後,將輸入的key作為value輸出,並根據value-list元素個數決定輸出的次數。輸出的key(即程式碼中的linenum)是一個全域性變數,它統計當前key的位次。需要注意的是這個程式中沒有配置Combiner,也就是在MapReduce過程中不使用Combiner。這主要是因為使用map和reduce就已經能夠完成任務了。

2.3 程式程式碼

    程式程式碼如下所示:

 

package com.hebut.mr;

 

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 Sort {

 

    //map將輸入中的value化成IntWritable型別,作為輸出的key

    public static class Map extends

        Mapper<Object,Text,IntWritable,IntWritable>{

        private static IntWritable data=new IntWritable();

       

        //實現map函式

        public void map(Object key,Text value,Context context)

                throws IOException,InterruptedException{

            String line=value.toString();

            data.set(Integer.parseInt(line));

            context.write(datanew IntWritable(1));

        }

       

    }

   

    //reduce將輸入中的key複製到輸出資料的key上,

    //然後根據輸入的value-list中元素的個數決定key的輸出次數

    //用全域性linenum來代表key的位次

    public static class Reduce extends

            Reducer<IntWritable,IntWritable,IntWritable,IntWritable>{

       

        private static IntWritable linenum = new IntWritable(1);

       

        //實現reduce函式

        public void reduce(IntWritable key,Iterable<IntWritable> values,Context context)

                throws IOException,InterruptedException{

            for(IntWritable val:values){

                context.write(linenum, key);

                linenum = new IntWritable(linenum.get()+1);

            }

           

        }

 

    }

   

    public static void main(String[] args) throws Exception{

        Configuration conf = new Configuration();

        //這句話很關鍵

        conf.set("mapred.job.tracker""192.168.1.2:9001");

       

        String[] ioArgs=new String[]{"sort_in","sort_out"};

     String[] otherArgs = new GenericOptionsParser(conf, ioArgs).getRemainingArgs();

     if (otherArgs.length != 2) {

     System.err.println("Usage: Data Sort <in> <out>");

         System.exit(2);

     }

     

     Job job = new Job(conf, "Data Sort");

     job.setJarByClass(Sort.class);

     

     //設定MapReduce處理類

     job.setMapperClass(Map.class);

     job.setReducerClass(Reduce.class);

     

     //設定輸出型別

     job.setOutputKeyClass(IntWritable.class);

     job.setOutputValueClass(IntWritable.class);

     

     //設定輸入和輸出目錄

     FileInputFormat.addInputPath(job, new Path(otherArgs[0]));

     FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));

     System.exit(job.waitForCompletion(true) ? 0 : 1);

     }

}

 

2.4 程式碼結果

1)準備測試資料

    通過Eclipse下面的"DFS Locations"在"/user/hadoop"目錄下建立輸入檔案"sort_in"資料夾(備註:"sort_out"不需要建立。)如圖2.4-1所示,已經成功建立。

              

圖2.4-1 建立"sort_in"                                                  圖2.4.2 上傳"file*.txt"

 

    然後在本地建立三個txt檔案,通過Eclipse上傳到"/user/hadoop/sort_in"資料夾中,三個txt檔案的內容如"例項描述"那三個檔案一樣。如圖2.4-2所示,成功上傳之後。

    從SecureCRT遠處檢視"Master.Hadoop"的也能證實我們上傳的三個檔案。

 

 

檢視兩個檔案的內容如圖2.4-3所示:

 

圖2.4-3 檔案"file*.txt"內容

2)檢視執行結果

    這時我們右擊Eclipse的"DFS Locations"中"/user/hadoop"資料夾進行重新整理,這時會發現多出一個"sort_out"資料夾,且裡面有3個檔案,然後開啟雙其"part-r-00000"檔案,會在Eclipse中間把內容顯示出來。如圖2.4-4所示。

 

圖2.4-4 執行結果

3、平均成績

    "平均成績"主要目的還是在重溫經典"WordCount"例子,可以說是在基礎上的微變化版,該例項主要就是實現一個計算學生平均成績的例子。

3.1 例項描述

  對輸入檔案中資料進行就算學生平均成績。輸入檔案中的每行內容均為一個學生姓名和他相應的成績,如果有多門學科,則每門學科為一個檔案。要求在輸出中每行有兩個間隔的資料,其中,第一個代表學生的姓名第二個代表其平均成績

    樣本輸入

    1)math:

 

張三    88

李四    99

王五    66

趙六    77

 

    2)china:

 

張三    78

李四    89

王五    96

趙六    67

 

    3)english:

 

張三    80

李四    82

王五    84

趙六    86

 

    樣本輸出

 

張三    82

李四    90

王五    82

趙六    76

 

3.2 設計思路

    計算學生平均成績是一個仿"WordCount"例子,用來重溫一下開發MapReduce程式的流程。程式包括兩部分的內容:Map部分和Reduce部分,分別實現了map和reduce的功能。

    Map處理的是一個純文字檔案,檔案中存放的資料時每一行表示一個學生的姓名和他相應一科成績。Mapper處理的資料是由InputFormat分解過的資料集,其中InputFormat的作用是將資料集切割成小資料集InputSplit,每一個InputSlit將由一個Mapper負責處理。此外,InputFormat中還提供了一個RecordReader的實現,並將一個InputSplit解析成<key,value>對提供給了map函式。InputFormat的預設值是TextInputFormat,它針對文字檔案,按行將文字切割成InputSlit,並用LineRecordReader將InputSplit解析成<key,value>對,key是行在文字中的位置,value是檔案中的一行。

    Map的結果會通過partion分發到Reducer,Reducer做完Reduce操作後,將通過以格式OutputFormat輸出。

    Mapper最終處理的結果對<key,value>,會送到Reducer中進行合併,合併的時候,有相同key的鍵/值對則送到同一個Reducer上。Reducer是所有使用者定製Reducer類地基礎,它的輸入是key和這個key對應的所有value的一個迭代器,同時還有Reducer的上下文。Reduce的結果由Reducer.Context的write方法輸出到檔案中。

3.3 程式程式碼

    程式程式碼如下所示:

 

package com.hebut.mr;

 

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<LongWritable, Text, Text, IntWritable> {

 

        // 實現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<Text, IntWritable, Text, IntWritable> {

        // 實現reduce函式

        public void reduce(Text key, Iterable<IntWritable> values,

                Context context) throws IOException, InterruptedException {

 

            int sum = 0;

            int count = 0;

 

            Iterator<IntWritable> 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();

        // 這句話很關鍵

        conf.set("mapred.job.tracker""192.168.1.2:9001");

 

        String[] ioArgs = new String[] { "score_in""score_out" };

        String[] otherArgs = new GenericOptionsParser(conf, ioArgs).getRemainingArgs();

        if (otherArgs.length != 2) {

            System.err.println("Usage: Score Average <in> <out>");

            System.exit(2);

        }

 

        Job job = new Job(conf, "Score Average");

        job.setJarByClass(Score.class);

 

        // 設定MapCombineReduce處理類

        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);

    }

}

 

3.4 程式碼結果

1)準備測試資料

    通過Eclipse下面的"DFS Locations"在"/user/hadoop"目錄下建立輸入檔案"score_in"資料夾(備註:"score_out"不需要建立。)如圖3.4-1所示,已經成功建立。

 

            

圖3.4-1 建立"score_in"                                                       圖3.4.2 上傳三門分數

 

    然後在本地建立三個txt檔案,通過Eclipse上傳到"/user/hadoop/score_in"資料夾中,三個txt檔案的內容如"例項描述"那三個檔案一樣。如圖3.4-2所示,成功上傳之後。

    備註:文字檔案的編碼為"UTF-8",預設為"ANSI",可以另存為時選擇,不然中文會出現亂碼

    從SecureCRT遠處檢視"Master.Hadoop"的也能證實我們上傳的三個檔案。

 

 

檢視三個檔案的內容如圖3.4-3所示:

 

圖3.4.3 三門成績的內容

2)檢視執行結果

    這時我們右擊Eclipse的"DFS Locations"中"/user/hadoop"資料夾進行重新整理,這時會發現多出一個"score_out"資料夾,且裡面有3個檔案,然後開啟雙其"part-r-00000"檔案,會在Eclipse中間把內容顯示出來。如圖3.4-4所示。

 

圖3.4-4 執行結果

4、單表關聯

    前面的例項都是在資料上進行一些簡單的處理,為進一步的操作打基礎。"單表關聯"這個例項要求給出的資料尋找關心的資料,它是對原始資料所包含資訊的挖掘。下面進入這個例項。

4.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

 

    家族樹狀關係譜:

 

 image

圖4.2-1 家族譜

    樣例輸出如下所示。

    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

 

4.2 設計思路

       分析這個例項,顯然需要進行單表連線,連線的是左表parent列和右表child列,且左表右表同一個表

  連線結果除去連線的兩列就是所需要的結果——"grandchild--grandparent"表。要用MapReduce解決這個例項,首先應該考慮如何實現自連線其次就是連線列設定最後結果整理

      考慮到MapReduce的shuffle過程會將相同的key會連線在一起,所以可以將map結果的key設定成待連線,然後列中相同的值就自然會連線在一起了。再與最開始的分析聯絡起來:

  要連線的是左表的parent列和右表的child列,且左表和右表是同一個表,所以在map階段讀入資料分割childparent之後,會將parent設定成keychild設定成value進行輸出,並作為左表再將同一對childparent中的child設定成keyparent設定成value進行輸出,作為右表。為了區分輸出中的左右表,需要在輸出的value加上左右表資訊,比如在value的String最開始處加上字元1表示左表,加上字元2表示右表。這樣在map的結果中就形成了左表和右表,然後在shuffle過程中完成連線。reduce接收到連線的結果,其中每個key的value-list就包含了"grandchild--grandparent"關係。取出每個key的value-list進行解析,將左表中的child放入一個陣列右表中的parent放入一個陣列,然後對兩個陣列求笛卡爾積就是最後的結果了。

4.3 程式程式碼

    程式程式碼如下所示。

 

package com.hebut.mr;

 

import java.io.IOException;

import java.util.*;

 

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 STjoin {

 

    public static int time = 0;

 

    /*

     * map將輸出分割childparent,然後正序輸出一次作為右表,

     * 反序輸出一次作為左表,需要注意的是在輸出的value中必須

     * 加上左右表的區別標識。

     */

    public static class Map extends Mapper<Object, Text, Text, Text> {

 

        // 實現map函式

        public void map(Object key, Text value, Context context)

                throws IOException, InterruptedException {

            String childname = new String();// 孩子名稱

            String parentname = new String();// 父母名稱

            String relationtype = new String();// 左右表標識

 

            // 輸入的一行預處理文字

            StringTokenizer itr=new StringTokenizer(value.toString());

            String[] values=new String[2];

            int i=0;

            while(itr.hasMoreTokens()){

                values[i]=itr.nextToken();

                i++;

            }

           

            if (values[0].compareTo("child") != 0) {

                childname = values[0];

                parentname = values[1];

 

                // 輸出左表

                relationtype = "1";

                context.write(new Text(values[1]), new Text(relationtype +

                        "+"+ childname + "+" + parentname));

 

                // 輸出右表

                relationtype = "2";

                context.write(new Text(values[0]), new Text(relationtype +

                        "+"+ childname + "+" + parentname));

            }

        }

 

    }

 

    public static class Reduce extends Reducer<Text, Text, Text, Text> {

 

        // 實現reduce函式

        public void reduce(Text key, Iterable<Text> values, Context context)

                throws IOException, InterruptedException {

 

            // 輸出表頭

            if (0 == time) {

                context.write(new Text("grandchild"), new Text("grandparent"));

                time++;

            }

 

            int grandchildnum = 0;

            String[] grandchild = new String[10];

            int grandparentnum = 0;

            String[] grandparent = new String[10];

 

            Iterator ite = values.iterator();

            while (ite.hasNext()) {

                String record = ite.next().toString();

                int len = record.length();

                int i = 2;

                if (0 == len) {

                    continue;

                }

 

                // 取得左右表標識

                char relationtype = record.charAt(0);

                // 定義孩子和父母變數

                String childname = new String();

                String parentname = new String();

 

                // 獲取value-listvaluechild

                while (record.charAt(i) != '+') {

                    childname += record.charAt(i);

                    i++;

                }

 

                i = i + 1;

 

                // 獲取value-listvalueparent

                while (i < len) {

                    parentname += record.charAt(i);

                    i++;

                }

 

                // 左表,取出child放入grandchildren

                if ('1' == relationtype) {

                    grandchild[grandchildnum] = childname;

                    grandchildnum++;

                }

 

                // 右表,取出parent放入grandparent

                if ('2' == relationtype) {

                    grandparent[grandparentnum] = parentname;

                    grandparentnum++;

                }

            }

 

            // grandchildgrandparent陣列求笛卡爾兒積

            if (0 != grandchildnum && 0 != grandparentnum) {

                for (int m = 0; m < grandchildnum; m++) {

                    for (int n = 0; n < grandparentnum; n++) {

                        // 輸出結果

                        context.write(new Text(grandchild[m]), new Text(grandparent[n]));

                    }

                }

            }

        }

    }

 

    public static void main(String[] args) throws Exception {

        Configuration conf = new Configuration();

        // 這句話很關鍵

        conf.set("mapred.job.tracker""192.168.1.2:9001");

 

        String[] ioArgs = new String[] { "STjoin_in""STjoin_out" };

        String[] otherArgs = new GenericOptionsParser(conf, ioArgs).getRemainingArgs();

        if (otherArgs.length != 2) {

            System.err.println("Usage: Single Table Join <in> <out>");

            System.exit(2);

        }

 

        Job job = new Job(conf, "Single Table Join");

        job.setJarByClass(STjoin.class);

 

        // 設定MapReduce處理類

        job.setMapperClass(Map.class);

        job.setReducerClass(Reduce.class);

 

        // 設定輸出型別

        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);

    }

}

 

4.4 程式碼結果

1)準備測試資料

    通過Eclipse下面的"DFS Locations"在"/user/hadoop"目錄下建立輸入檔案"STjoin_in"資料夾(備註:"STjoin_out"不需要建立。)如圖4.4-1所示,已經成功建立。

 

                  

圖4.4-1 建立"STjoin_in"                                       圖4.4.2 上傳"child-parent"表

 

    然後在本地建立一個txt檔案,通過Eclipse上傳到"/user/hadoop/STjoin_in"資料夾中,一個txt檔案的內容如"例項描述"那個檔案一樣。如圖4.4-2所示,成功上傳之後。

    從SecureCRT遠處檢視"Master.Hadoop"的也能證實我們上傳的檔案,顯示其內容如圖4.4-3所示:

 

圖4.4-3 表"child-parent"內容

    2)執行詳解

    (1)Map處理:

    map函式輸出結果如下所示。

 

child        parent                àà                    忽略此行

Tom        Lucy                   àà                <Lucy,1+Tom+Lucy>

                                                    <Tom,2+Tom+Lucy >

Tom        Jack                    àà                <Jack,1+Tom+Jack>

                                                    <Tom,2+Tom+Jack>

Jone        Lucy                 àà                <Lucy,1+Jone+Lucy>

                                                    <Jone,2+Jone+Lucy>

Jone        Jack                    àà                <Jack,1+Jone+Jack>

                                                    <Jone,2+Jone+Jack>

Lucy        Mary                   àà                <Mary,1+Lucy+Mary>

                                                    <Lucy,2+Lucy+Mary>

Lucy        Ben                    àà                <Ben,1+Lucy+Ben>

                                                     <Lucy,2+Lucy+Ben>

Jack        Alice                    àà                <Alice,1+Jack+Alice>

                                                      <Jack,2+Jack+Alice>

Jack        Jesse                   àà                <Jesse,1+Jack+Jesse>

                                                      <Jack,2+Jack+Jesse>

Terry        Alice                   àà                <Alice,1+Terry+Alice>

                                                      <Terry,2+Terry+Alice>

Terry        Jesse                  àà                <Jesse,1+Terry+Jesse>

                                                      <Terry,2+Terry+Jesse>

Philip        Terry                  àà                <Terry,1+Philip+Terry>

                                                      <Philip,2+Philip+Terry>

Philip        Alma                   àà                <Alma,1+Philip+Alma>

                                                      <Philip,2+Philip+Alma>

Mark        Terry                   àà                <Terry,1+Mark+Terry>

                                                      <Mark,2+Mark+Terry>

Mark        Alma                 àà                <Alma,1+Mark+Alma>

                                                      <Mark,2+Mark+Alma>

 

    (2)Shuffle處理

    在shuffle過程中完成連線。

 

map函式輸出

排序結果

shuffle連線

<Lucy1+Tom+Lucy>

<Tom2+Tom+Lucy>

<Jack1+Tom+Jack>

<Tom2+Tom+Jack>

<Lucy1+Jone+Lucy>

<Jone2+Jone+Lucy>

<Jack1+Jone+Jack>

<Jone2+Jone+Jack>

<Mary1+Lucy+Mary>

<Lucy2+Lucy+Mary>

<Ben1+Lucy+Ben>

<Lucy2+Lucy+Ben>

<Alice1+Jack+Alice>

<Jack2+Jack+Alice>

<Jesse1+Jack+Jesse>

<Jack2+Jack+Jesse>

<Alice1+Terry+Alice>

<Terry2+Terry+Alice>

<Jesse1+Terry+Jesse>

<Terry2+Terry+Jesse>

<Terry1+Philip+Terry>

<Philip2+Philip+Terry>

<Alma1+Philip+Alma>

<Philip2+Philip+Alma>

<Terry1+Mark+Terry>

<Mark2+Mark+Terry>

<Alma1+Mark+Alma>

<Mark2+Mark+Alma>

<Alice1+Jack+Alice>

<Alice1+Terry+Alice>

<Alma1+Philip+Alma>

<Alma1+Mark+Alma>

<Ben1+Lucy+Ben>

<Jack1+Tom+Jack>

<Jack1+Jone+Jack>

<Jack2+Jack+Alice>

<Jack2+Jack+Jesse>

<Jesse1+Jack+Jesse>

<Jesse1+Terry+Jesse>

<Jone2+Jone+Lucy>

<Jone2+Jone+Jack>

<Lucy1+Tom+Lucy>

<Lucy1+Jone+Lucy>

<Lucy2+Lucy+Mary>

<Lucy2+Lucy+Ben>

<Mary1+Lucy+Mary>

<Mark2+Mark+Terry>

<Mark2+Mark+Alma>

<Philip2+Philip+Terry>

<Philip2+Philip+Alma>

<Terry2+Terry+Alice>

<Terry2+Terry+Jesse>

<Terry1+Philip+Terry>

<Terry1+Mark+Terry>

<Tom2+Tom+Lucy>

<Tom2+Tom+Jack>

<Alice1+Jack+Alice

        1+Terry+Alice 

        1+Philip+Alma

        1+Mark+Alma >

<Ben1+Lucy+Ben>

<Jack1+Tom+Jack

        1+Jone+Jack

        2+Jack+Alice

        2+Jack+Jesse >

<Jesse1+Jack+Jesse

        1+Terry+Jesse >

<Jone2+Jone+Lucy

        2+Jone+Jack>

<Lucy1+Tom+Lucy

        1+Jone+Lucy

        2+Lucy+Mary

        2+Lucy+Ben>

<Mary1+Lucy+Mary

        2+Mark+Terry

        2+Mark+Alma>

<Philip2+Philip+Terry

        2+Philip+Alma>

<Terry2+Terry+Alice

        2+Terry+Jesse

        1+Philip+Terry

        1+Mark+Terry>

<Tom2+Tom+Lucy

        2+Tom+Jack>

 

    (3)Reduce處理

    首先由語句"0 != grandchildnum && 0 != grandparentnum"得知,只要在"value-list"中沒有左表或者右表,則不會做處理,可以根據這條規則去除無效shuffle連線

 

無效shuffle連線

有效shuffle連線

<Alice1+Jack+Alice

        1+Terry+Alice 

        1+Philip+Alma

        1+Mark+Alma >

<Ben1+Lucy+Ben>

<Jesse1+Jack+Jesse

        1+Terry+Jesse >

<Jone2+Jone+Lucy

        2+Jone+Jack>

<Mary1+Lucy+Mary

        2+Mark+Terry

        2+Mark+Alma>

<Philip2+Philip+Terry

        2+Philip+Alma>

<Tom2+Tom+Lucy

        2+Tom+Jack>

<Jack1+Tom+Jack

        1+Jone+Jack

        2+Jack+Alice

        2+Jack+Jesse >

<Lucy1+Tom+Lucy

        1+Jone+Lucy

        2+Lucy+Mary

        2+Lucy+Ben>

<Terry2+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++;

}

 

    針對一條資料進行分析:

 

<Jack,1+Tom+Jack,

        1+Jone+Jack,

        2+Jack+Alice,

        2+Jack+Jesse >

 

    分析結果左表用"字元1"表示,右表用"字元2"表示,上面的<key,value-list>中的"key"表示左表與右表連線鍵。而"value-list"表示以"key"連線左表與右表相關資料

    根據上面針對左表與右表不同的處理規則,取得兩個陣列的資料如下所示:

 

grandchild

TomJonegrandchild[grandchildnum] = childname;

grandparent

AliceJessegrandparent[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]));

    }

}

 

image  

 

處理結果如下面所示:

 

Tom        Jesse

Tom        Alice

Jone        Jesse

Jone        Alice 

    其他的有效shuffle連線處理都是如此

3)檢視執行結果

    這時我們右擊Eclipse的"DFS Locations"中"/user/hadoop"資料夾進行重新整理,這時會發現多出一個"STjoin_out"資料夾,且裡面有3個檔案,然後開啟雙其"part-r-00000"檔案,會在Eclipse中間把內容顯示出來。如圖4.4-4所示。

 

圖4.4-4 執行結果

5、多表關聯

    多表關聯和單表關聯類似,它也是通過對原始資料進行一定的處理,從其中挖掘出關心的資訊。下面進入這個例項。

5.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

 

5.2 設計思路

    多表關聯和單表關聯相似,都類似於資料庫中的自然連線。相比單表關聯,多表關聯的左右表和連線列更加清楚。所以可以採用和單表關聯的相同處理方式,map識別出輸入的行屬於哪個表之後,對其進行分割,將連線的列值儲存在key中,另一列和左右表標識儲存在value中,然後輸出。reduce拿到連線結果之後,解析value內容,根據標誌將左右表內容分開存放,然後求笛卡爾積,最後直接輸出。

    這個例項的具體分析參考單表關聯例項。下面給出程式碼。

5.3 程式程式碼

    程式程式碼如下所示:

 

package com.hebut.mr;

 

import java.io.IOException;

import java.util.*;

 

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 MTjoin {

 

    public static int time = 0;

 

    /*

     * map中先區分輸入行屬於左表還是右表,然後對兩列值進行分割,

     * 儲存連線列在key值,剩餘列和左右表標誌在value中,最後輸出

     */

    public static class Map extends Mapper<Object, Text, Text, Text> {

 

        // 實現map函式

        public void map(Object key, Text value, Context context)

                throws IOException, InterruptedException {

            String line = value.toString();// 每行檔案

            String relationtype = new String();// 左右表標識

 

            // 輸入檔案首行,不處理

            if (line.contains("factoryname") == true

                    || line.contains("addressed") == true) {

                return;

            }

 

            // 輸入的一行預處理文字

            StringTokenizer itr = new StringTokenizer(line);

            String mapkey = new String();

            String mapvalue = new String();

            int i = 0;

            while (itr.hasMoreTokens()) {

                // 先讀取一個單詞

                String token = itr.nextToken();

                // 判斷該地址ID就把存到"values[0]"

                if (token.charAt(0) >= '0' && token.charAt(0) <= '9') {

                    mapkey = token;

                    if (i > 0) {

                        relationtype = "1";

                    } else {

                        relationtype = "2";

                    }

                    continue;

                }

 

                // 存工廠名

                mapvalue += token + " ";

                i++;

            }

 

            // 輸出左右表

            context.write(new Text(mapkey), new Text(relationtype + "+"+ mapvalue));

        }

    }

 

    /*

     * reduce解析map輸出,將value中資料按照左右表分別儲存,

  * 然後求出笛卡爾積,並輸出。

     */

    public static class Reduce extends Reducer<Text, Text, Text, Text> {

 

        // 實現reduce函式

        public void reduce(Text key, Iterable<Text> values, Context context)

                throws IOException, InterruptedException {

 

            // 輸出表頭

            if (0 == time) {

                context.write(new Text("factoryname"), new Text("addressname"));

                time++;

            }

 

            int factorynum = 0;

            String[] factory = new String[10];

            int addressnum = 0;

            String[] address = new String[10];

 

            Iterator ite = values.iterator();

            while (ite.hasNext()) {

                String record = ite.next().toString();

                int len = record.length();

                int i = 2;

                if (0 == len) {

                    continue;

                }

 

                // 取得左右表標識

                char relationtype = record.charAt(0);

 

                // 左表

                if ('1' == relationtype) {

                    factory[factorynum] = record.substring(i);

                    factorynum++;

                }

 

                // 右表

                if ('2' == relationtype) {

                    address[addressnum] = record.substring(i);

                    addressnum++;

                }

            }

 

            // 求笛卡爾積

            if (0 != factorynum && 0 != addressnum) {

                for (int m = 0; m < factorynum; m++) {

                    for (int n = 0; n < addressnum; n++) {

                        // 輸出結果

                        context.write(new Text(factory[m]),

                                new Text(address[n]));

                    }

                }

            }

 

        }

    }

 

    public static void main(String[] args) throws Exception {

        Configuration conf = new Configuration();

        // 這句話很關鍵

        conf.set("mapred.job.tracker""192.168.1.2:9001");

 

        String[] ioArgs = new String[] { "MTjoin_in""MTjoin_out" };

        String[] otherArgs = new GenericOptionsParser(conf, ioArgs).getRemainingArgs();

        if (otherArgs.length != 2) {

            System.err.println("Usage: Multiple Table Join <in> <out>");

            System.exit(2);

        }

 

        Job job = new Job(conf, "Multiple Table Join");

        job.setJarByClass(MTjoin.class);

 

        // 設定MapReduce處理類

        job.setMapperClass(Map.class);

        job.setReducerClass(Reduce.class);

 

        // 設定輸出型別

        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);

    }

}

 

5.4 程式碼結果

1)準備測試資料

    通過Eclipse下面的"DFS Locations"在"/user/hadoop"目錄下建立輸入檔案"MTjoin_in"資料夾(備註:"MTjoin_out"不需要建立。)如圖5.4-1所示,已經成功建立。

 

                 

圖5.4-1 建立"MTjoin_in"                                                             圖5.4.2 上傳兩個資料表

 

    然後在本地建立兩個txt檔案,通過Eclipse上傳到"/user/hadoop/MTjoin_in"資料夾中,兩個txt檔案的內容如"例項描述"那兩個檔案一樣。如圖5.4-2所示,成功上傳之後。

    從SecureCRT遠處檢視"Master.Hadoop"的也能證實我們上傳的兩個檔案。

 

圖5.4.3 兩個資料表的內容

2)檢視執行結果

    這時我們右擊Eclipse的"DFS Locations"中"/user/hadoop"資料夾進行重新整理,這時會發現多出一個"MTjoin_out"資料夾,且裡面有3個檔案,然後開啟雙其"part-r-00000"檔案,會在Eclipse中間把內容顯示出來。如圖5.4-4所示。

 

圖5.4-4 執行結果

6、倒排索引

    "倒排索引"是文件檢索系統最常用資料結構,被廣泛地應用於全文搜尋引擎。它主要是用來儲存某個單詞(或片語)一個文件或一組文件中的儲存位置對映,即提供了一種根據內容來查詢文件方式。由於不是根據文件來確定文件所包含的內容,而是進行相反的操作,因而稱為倒排索引(Inverted Index)。

6.1 例項描述

    通常情況下,倒排索引由一個單詞(或片語)以及相關的文件列表組成,文件列表中的文件或者是標識文件的ID號,或者是指文件所在位置的URL,如圖6.1-1所示。

 image

圖6.1-1 倒排索引結構

    從圖6.1-1可以看出,單詞1出現在{文件1,文件4,文件13,……}中,單詞2出現在{文件3,文件5,文件15,……}中,而單詞3出現在{文件1,文件8,文件20,……}中。在實際應用中,還需要每個文件新增一個權值,用來指出每個文件與搜尋內容的相關度,如圖6.1-2所示。

 

 image

圖6.1-2 新增權重的倒排索引

    最常用的是使用詞頻作為權重,即記錄單詞在文件中出現的次數。以英文為例,如圖6.1-3所示,索引檔案中的"MapReduce"一行表示:"MapReduce"這個單詞在文字T0中出現過1次,T1中出現過1次,T2中出現過2次。當搜尋條件為"MapReduce"、"is"、"Simple"時,對應的集合為:{T0,T1,T2}∩{T0,T1}∩{T0,T1}={T0,T1},即文件T0和T1包含了所要索引的單詞,而且只有T0是連續的。

 

 image

圖6.1-3 倒排索引示例

    更復雜的權重還可能要記錄單詞在多少個文件中出現過,以實現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;

 

6.2 設計思路

    實現"倒排索引"只要關注的資訊為:單詞文件URL詞頻,如圖3-11所示。但是在實現過程中,索引檔案的格式與圖6.1-3會略有所不同,以避免重寫OutPutFormat類。下面根據MapReduce的處理過程給出倒排索引設計思路

    1)Map過程

    首先使用預設的TextInputFormat類對輸入檔案進行處理,得到文字中每行偏移量及其內容。顯然,Map過程首先必須分析輸入的<key,value>對,得到倒排索引中需要的三個資訊:單詞、文件URL和詞頻,如圖6.2-1所示。

 image

圖6.2-1 Map過程輸入/輸出

 

  這裡存在兩個問題第一,<key,value>對只能有兩個值,在不使用Hadoop自定義資料型別的情況下,需要根據情況將其中兩個值合併成一個值,作為key或value值;第二,通過一個Reduce過程無法同時完成詞頻統計生成文件列表,所以必須增加一個Combine過程完成詞頻統計

    這裡講單詞和URL組成key值(如"MapReduce:file1.txt"),將詞頻作為value,這樣做的好處是可以利用MapReduce框架自帶的Map端排序,將同一文件相同單詞詞頻組成列表,傳遞給Combine過程,實現類似於WordCount的功能。

    2)Combine過程

    經過map方法處理後,Combine過程將key值相同的value值累加,得到一個單詞在文件在文件中的詞頻,如圖6.2-2所示。如果直接將圖6.2-2所示的輸出作為Reduce過程的輸入,在Shuffle過程時將面臨一個問題:所有具有相同單詞的記錄(由單詞、URL和詞頻組成)應該交由同一個Reducer處理,但當前的key值無法保證這一點,所以必須修改key值和value值。這次將單詞作為key值,URL和詞頻組value值(如"file1.txt:1")。這樣做的好處是可以利用MapReduce框架預設的HashPartitioner類完成Shuffle過程,將相同單詞所有記錄傳送給同一個Reducer進行處理

 

 image

圖6.2-2 Combine過程輸入/輸出

    3)Reduce過程

    經過上述兩個過程後,Reduce過程只需將相同key值的value值組合成倒排索引檔案所需的格式即可,剩下的事情就可以直接交給MapReduce框架進行處理了。如圖6.2-3所示。索引檔案的內容除分隔符外與圖6.1-3解釋相同。

    4)需要解決的問題

    本例項設計的倒排索引在檔案數目沒有限制,但是單詞檔案不宜過大(具體值與預設HDFS塊大小及相關配置有關),要保證每個檔案對應一個split。否則,由於Reduce過程沒有進一步統計詞頻,最終結可能出現詞頻未統計完全單詞。可以通過重寫InputFormat類將每個檔案為一個split,避免上述情況。或者執行兩次MapReduce第一次MapReduce用於統計詞頻第二次MapReduce用於生成倒排索引。除此之外,還可以利用複合鍵值對等實現包含更多資訊的倒排索引。

 

 image

圖6.2-3 Reduce過程輸入/輸出

6.3 程式程式碼

  程式程式碼如下所示:

 

package com.hebut.mr;

 

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<Object, Text, Text, Text> {

 

        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 {

 

            // 獲得<key,value>對所屬的FileSplit物件

            split = (FileSplit) context.getInputSplit();

 

            StringTokenizer itr = new StringTokenizer(value.toString());

 

            while (itr.hasMoreTokens()) {

                // key值由單詞和URL組成,如"MapReducefile1.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");

 

                context.write(keyInfovalueInfo);

            }

        }

    }

 

    public static class Combine extends Reducer<Text, Text, Text, Text> {

 

        private Text info = new Text();

 

        // 實現reduce函式

        public 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);

        }

    }

 

    public static class Reduce extends Reducer<Text, Text, Text, Text> {

 

        private Text result = new Text();

 

        // 實現reduce函式

        public void reduce(Text key, Iterable<Text> 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();

        // 這句話很關鍵

        conf.set("mapred.job.tracker""192.168.1.2:9001");

 

        String[] ioArgs = new String[] { "index_in""index_out" };

        String[] otherArgs = new GenericOptionsParser(conf, ioArgs)

                .getRemainingArgs();

        if (otherArgs.length != 2) {

            System.err.println("Usage: Inverted Index <in> <out>");

            System.exit(2);

        }

 

        Job job = new Job(conf, "Inverted Index");

        job.setJarByClass(InvertedIndex.class);

 

        // 設定MapCombineReduce處理類

        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);

    }

}

 

6.4 程式碼結果

1)準備測試資料

    通過Eclipse下面的"DFS Locations"在"/user/hadoop"目錄下建立輸入檔案"index_in"資料夾(備註:"index_out"不需要建立。)如圖6.4-1所示,已經成功建立。

 

                

圖6.4-1 建立"index_in"                                             圖6.4.2 上傳"file*.txt"

 

    然後在本地建立三個txt檔案,通過Eclipse上傳到"/user/hadoop/index_in"資料夾中,三個txt檔案的內容如"例項描述"那三個檔案一樣。如圖6.4-2所示,成功上傳之後。

    從SecureCRT遠處檢視"Master.Hadoop"的也能證實我們上傳的三個檔案。

 

圖6.4.3 三個"file*.txt"的內容

2)檢視執行結果

    這時我們右擊Eclipse的"DFS Locations"中"/user/hadoop"資料夾進行重新整理,這時會發現多出一個"index_out"資料夾,且裡面有3個檔案,然後開啟雙其"part-r-00000"檔案,會在Eclipse中間把內容顯示出來。如圖6.4-4所示。

 

圖6.4-4 執行結果

 

 

  文章下載地址:http://files.cnblogs.com/xia520pi/HadoopCluster_Vol.9.rar


相關文章