大數(shù)據(jù)之Hadoop學(xué)習(xí)——動手實戰(zhàn)學(xué)習(xí)MapReduce編程實例

前言

這里放一個我學(xué)習(xí)MapReduce的編程實例項目吧,本來是想把這些分開寫成多篇文章的,能夠詳細敘述我學(xué)習(xí)過程中感想。但無奈,時間不夠,只好在Github上創(chuàng)建了該項目,在代碼中由較為詳細的注釋,我想也足夠了吧。
josonle/MapReduce-Demo
該項目有些題目是參考了網(wǎng)上幾篇博客,但代碼實現(xiàn)是本人實現(xiàn)的。其次,所謂的MapReduce學(xué)習(xí)流程是參照老師上課所講的PPT上的流程【某985大數(shù)據(jù)課程PPT】,我想老師以這樣的流程授課肯定是有道理的。項目中也放了老師提供的幾個參考Demo文件。


目錄(目錄不可用,見諒。項目中也付了這篇文檔)

MapReduce編程實例

1.自定義對象序列化

需求分析

需要統(tǒng)計手機用戶流量日志,日志內(nèi)容實例:

flowdata.log

要把同一個用戶的上行流量、下行流量進行累加,并計算出綜合 。例如上面的13897230503有兩條記錄,就要對這兩條記錄進行累加,計算總和,得到:13897230503,500,1600,2100

報錯:Exception in thread "main" java.lang.IllegalArgumentException: Wrong FS: hdfs://192.168.17.10:9000/workspace/flowStatistics/output, expected: file:///

解決:1、將core-site.xml 和hdfs-site.xml拷貝到項目里去就可以,原因是訪問遠程的HDFS 需要通過URI來獲得FileSystem
    2、在項目中,Configuration對象設(shè)置fs.defaultFS 【推薦這個,**大小寫別拼錯,我就是拼錯了找了半天**】

        String namenode_ip = "192.168.17.10";
        String hdfs = "hdfs://"+namenode_ip+":9000";
        Configuration conf = new Configuration();
        conf.set("fs.defaultFS", hdfs);

解答

一、正常處理即可,不過在處理500 1400 這種時靈活變通一下即可
public static class FlowMapper extends Mapper<Object, Text, Text, Text>{
        
        public void map(Object key,Text value,Context context) throws IOException, InterruptedException {
            String[] strs = value.toString().split("\t");
            Text phone = new Text(strs[0]);
            Text flow = new Text(strs[1]+"\t"+strs[2]);
            context.write(phone, flow);
        }
    }
    
    public static class FlowReducer extends Reducer<Text, Text, Text, Text>{
        public void reduce(Text key,Iterable<Text> values,Context context) throws IOException, InterruptedException {
            int upFlow = 0;
            int downFlow = 0;
            
            for (Text value : values) {
                String[] strs = value.toString().split("\t");
                upFlow += Integer.parseInt(strs[0].toString());
                downFlow += Integer.parseInt(strs[1].toString());
            }
            int sumFlow = upFlow+downFlow;
            
            context.write(key,new Text(upFlow+"\t"+downFlow+"\t"+sumFlow));
        }
    }

二、自定義一個實現(xiàn)Writable接口的可序列化的對象Flow,包含數(shù)據(jù)形式如 upFlow downFlow sumFlow
public static class FlowWritableMapper extends Mapper<Object, Text, Text, FlowWritable> {
        public void map(Object key,Text value,Context context) throws IOException, InterruptedException {
            String[] strs = value.toString().split("\t");
            Text phone = new Text(strs[0]);
            FlowWritable flow = new FlowWritable(Integer.parseInt(strs[1]),Integer.parseInt(strs[2]));
            context.write(phone, flow);
        }
    }
    public static class FlowWritableReducer extends Reducer<Text, FlowWritable, Text, FlowWritable>{
        public void reduce(Text key,Iterable<FlowWritable> values,Context context) throws IOException, InterruptedException {
            int upFlow = 0;
            int downFlow = 0;
            
            for (FlowWritable value : values) {
                upFlow += value.getUpFlow();
                downFlow += value.getDownFlow();
            }
            
            context.write(key,new FlowWritable(upFlow,downFlow));
        }
    }
    
    public static class FlowWritable implements Writable{
        private int upFlow,downFlow,sumFlow;

        public FlowWritable(int upFlow,int downFlow) {
            this.upFlow = upFlow;
            this.downFlow = downFlow;
            this.sumFlow = upFlow+downFlow;
        }
        
        public int getDownFlow() {
            return downFlow;
        }

        public void setDownFlow(int downFlow) {
            this.downFlow = downFlow;
        }

        public int getUpFlow() {
            return upFlow;
        }

        public void setUpFlow(int upFlow) {
            this.upFlow = upFlow;
        }

        public int getSumFlow() {
            return sumFlow;
        }

        public void setSumFlow(int sumFlow) {
            this.sumFlow = sumFlow;
        }
        // writer和readFields方法務(wù)必實現(xiàn),序列化數(shù)據(jù)的關(guān)鍵
        @Override
        public void write(DataOutput out) throws IOException {
            // TODO Auto-generated method stub
            out.writeInt(upFlow);
            out.writeInt(downFlow);
            out.writeInt(sumFlow);
        }

        @Override
        public void readFields(DataInput in) throws IOException {
            // TODO Auto-generated method stub
            upFlow = in.readInt();
            downFlow = in.readInt();
            sumFlow = in.readInt();
        }

        @Override
        public String toString() {
            // TODO Auto-generated method stub
            return upFlow+"\t"+downFlow+"\t"+sumFlow;
        }
    }

注意: 要根據(jù)具體情況在job中設(shè)置Mapper、Reducer類及輸出的key、value類型
具體見代碼

2.數(shù)據(jù)去重

需求分析

需求很簡單,就是把文件中重復(fù)數(shù)據(jù)去掉。比如說統(tǒng)計類似如下文件中不包含重復(fù)日期數(shù)據(jù)的日期

2017-02-14 1
2016-02-01 2
2017-07-10 3
2016-02-26 4
2015-01-19 5
2016-04-29 6
2016-05-10 7
2015-11-20 8
2017-05-23 9
2014-02-26 10

解答思路

只要搞清楚了MR的流程這個就很簡單,reducer的輸入類似<key3,[v1,v2,v3...]>,這個地方輸入的key3是沒有重復(fù)值的。所以利用這一點,Mapper輸出的key保存日期數(shù)據(jù),value置為空即可 【這里可以使用NullWritable類型】

還有就是,不一定是日期去重,去重一行數(shù)據(jù)也是如此,key保存這一行數(shù)據(jù)即可

public static class DateDistinctMapper extends Mapper<Object, Text, Text, NullWritable> {       
        public void map(Object key, Text value, Context context ) 
                throws IOException, InterruptedException {
            String[] strs = value.toString().split(" ");
            Text date = new Text(strs[0]);//取到日期作為key
            context.write(date, NullWritable.get());
        }
    }
  
public static class DateDistinctReducer extends Reducer<Text,NullWritable,Text,NullWritable>{
    
        public void reduce(Text key, Iterable<NullWritable> values, Context context) 
                throws IOException, InterruptedException {
            context.write(key, NullWritable.get());
        }
    }

3.數(shù)據(jù)排序、二次排序

需求分析

這一類問題很多,像學(xué)生按成績排序,手機用戶流量按上行流量升序,下行流量降序排序等等

  1. 日期計數(shù)升序排序

  2. 日期計數(shù)降序排序

    //日期 日期出現(xiàn)的次數(shù)
    2015-01-27   7
    2015-01-28   3
    2015-01-29   7
    2015-01-30   6
    2015-01-31   7
    2015-02-01   15
    2015-02-02   10
    2015-02-03   9
    2015-02-04   12
    2015-02-05   14
    
  1. 手機用戶流量按上行流量升序,下行流量降序排序

解答思路

MapReduce是默認會對key進行升序排序的,可以利用這一點實現(xiàn)某些排序

  • 單列排序
    • 升序還是降序排序
    • 可以利用Shuffle默認對key排序的規(guī)則;
    • 自定義繼承WritableComparator的排序類,實現(xiàn)compare方法
  • 二次排序
    • 實現(xiàn)可序列化的比較類WritableComparable<T>,并實現(xiàn)compareTo方法(同樣可指定升序降序)
日期按計數(shù)升序排序
public static class SortMapper extends Mapper<Object, Text, IntWritable, Text> {
        private IntWritable num = new IntWritable();

        public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
            String[] strs = value.toString().split("\t");
            num.set(Integer.parseInt(strs[1]));
            // 將次數(shù)作為key進行升序排序
            context.write(num, new Text(strs[0]));
            System.out.println(num.get()+","+strs[0]);
        }
    }

    public static class SortReducer extends Reducer<IntWritable, Text, Text, IntWritable> {

        public void reduce(IntWritable key, Iterable<Text> values, Context context)
                throws IOException, InterruptedException {
            for (Text value : values) {
                // 排序后再次顛倒k-v,將日期作為key
                System.out.println(value.toString()+":"+key.get());
                context.write(value, key);
            }
        }
    }
日期按計數(shù)降序排序

實現(xiàn)自定義的排序比較器,繼承WritableComparator類,并實現(xiàn)其compare方法

public static class MyComparator extends WritableComparator {
        public MyComparator() {
            // TODO Auto-generated constructor stub
            super(IntWritable.class, true);
        }

        @Override
        @SuppressWarnings({ "rawtypes", "unchecked" }) // 不檢查類型
        public int compare(WritableComparable a, WritableComparable b) {
            // CompareTo方法,返回值為1則降序,-1則升序
            // 默認是a.compareTo(b),a比b小返回-1,現(xiàn)在反過來返回1,就變成了降序
            return b.compareTo(a);
    }

所使用的Mapper、Reducer同上面升序排序的,其次,要在main函數(shù)中指定自定義的排序比較器

job.setSortComparatorClass(MyComparator.class);

手機用戶流量按上行流量升序,下行流量降序排序

同第一個實例類似,要自定義對象序列化,同時也要可比較,實現(xiàn)WritableComparable接口,并實現(xiàn)CompareTo方法

我這里是將之前統(tǒng)計好的用戶流量數(shù)據(jù)作為輸入數(shù)據(jù)

public static class MySortKey implements WritableComparable<MySortKey> {
        private int upFlow;
        private int downFlow;
        private int sumFlow;

        public void FlowSort(int up, int down) {
            upFlow = up;
            downFlow = down;
            sumFlow = up + down;
        }

        public int getUpFlow() {
            return upFlow;
        }
        public void setUpFlow(int upFlow) {
            this.upFlow = upFlow;
        }
        public int getDownFlow() {
            return downFlow;
        }
        public void setDownFlow(int downFlow) {
            this.downFlow = downFlow;
        }
        public int getSumFlow() {
            return sumFlow;
        }
        public void setSumFlow(int sumFlow) {
            this.sumFlow = sumFlow;
        }

        @Override
        public void write(DataOutput out) throws IOException {
            // TODO Auto-generated method stub
            out.writeInt(upFlow);
            out.writeInt(downFlow);
            out.writeInt(sumFlow);
        }

        @Override
        public void readFields(DataInput in) throws IOException {
            // TODO Auto-generated method stub
            upFlow = in.readInt();
            downFlow = in.readInt();
            sumFlow = in.readInt();
        }

        @Override
        public int compareTo(MySortKey o) {
            if ((this.upFlow - o.upFlow) == 0) {// 上行流量相等,比較下行流量
                return o.downFlow - this.downFlow;// 按downFlow降序排序
            } else {
                return this.upFlow - o.upFlow;// 按upFlow升序排
            }
        }

        @Override
        public String toString() {
            // TODO Auto-generated method stub
            return upFlow + "\t" + downFlow + "\t" + sumFlow;
        }
    }

    public static class SortMapper extends Mapper<Object, Text, MySortKey, Text> {
        Text phone = new Text();
        MySortKey mySortKey = new MySortKey();

        public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
            String[] lists = value.toString().split("\t");
            phone.set(lists[0]);
            mySortKey.setUpFlow(Integer.parseInt(lists[1]));
            mySortKey.setDownFlow(Integer.parseInt(lists[2]));
            context.write(mySortKey, phone);// 調(diào)換手機號和流量計數(shù),后者作為排序鍵
        }
    }

    public static class SortReducer extends Reducer<MySortKey, Text, Text, MySortKey> {
        public void reduce(MySortKey key, Iterable<Text> values, Context context)
                throws IOException, InterruptedException {
            for (Text value : values) {
                System.out.println(value.toString()+","+key.toString());
                context.write(value, key);// 再次把手機號和流量計數(shù)調(diào)換
            }
        }
    }

4.自定義分區(qū)

需求分析

還是以上個例子的手機用戶流量日志為例,在上個例子的統(tǒng)計需要基礎(chǔ)上添加一個新需求:按省份統(tǒng)計,不同省份的手機號放到不同的文件里。

例如137表示屬于河北,138屬于河南,那么在結(jié)果輸出時,他們分別在不同的文件中。

解答思路

挺簡單的,看過我之前結(jié)合源碼解讀MapReduce過程的話,就知道這其實就是一個分區(qū)的問題。定義自己的分區(qū)規(guī)則,一個分區(qū)會對應(yīng)一個reduce,會輸出到一個文件。

而你需要做的就是基礎(chǔ)partitioner類,并實現(xiàn)getPartition方法,其余過程同第一個例子

// 自定義分區(qū)類
public static class PhoneNumberPartitioner extends Partitioner<Text, FlowWritable> {
        private static HashMap<String, Integer> numberDict = new HashMap<>();
        static {
            numberDict.put("133", 0);
            numberDict.put("135", 1);
            numberDict.put("137", 2);
            numberDict.put("138", 3);
        }

        @Override
        public int getPartition(Text key, FlowWritable value, int numPartitions) {
            String num = key.toString().substring(0, 3);
            // 借助HashMap返回不同手機段對應(yīng)的分區(qū)號
            // 也可以直接通過if判斷,如
            // 根據(jù)年份對數(shù)據(jù)進行分區(qū),返回不同分區(qū)號
            // if (key.toString().startsWith("133")) return 0 % numPartitions;
            return numberDict.getOrDefault(num, 4);
        }
    }

注意: main函數(shù)中要指定自定義分區(qū)類,以及Reducer task數(shù)量(一個分區(qū)對應(yīng)一個reduce任務(wù),一個Reduce任務(wù)對應(yīng)一個輸出文件)

// 設(shè)置分區(qū)類,及Reducer數(shù)目
job.setPartitionerClass(PhoneNumberPartitioner.class);
job.setNumReduceTasks(4);

[圖片上傳失敗...(image-591982-1544427279895)]

增加ReduceTask數(shù)量可看到生成的文件數(shù)也增加了,不過文件內(nèi)容為空

5.計算出每組訂單中金額最大的記錄

需求分析

有如下訂單數(shù)據(jù):

img

需要求出每一個訂單中成交金額最大的一筆交易。

思路解答

實際上是求最大值、最小值的問題,一拿到題,大概會冒出兩種思路吧

  1. 先排序(升序),Reduce端取第一條就是最小值,最后一條是最大值
  2. 不排序,在Reduce端不斷循環(huán)作比較,也可以求得最值

但問題還涉及到每一個訂單中的最大值,這就是分組的問題。比如說這里,同一訂單號視為一組,在一組中找最大

先定義一個可序列化且可比較的對象Pair,用來存order_id,amount(只涉及這兩個變量)。Mapper端輸出類似

Key2 Value2
{order_0000001,222.8} null
{order_0000001,25.8} null
{order_0000002,522.8} null
{order_0000002,122.4} null
{order_0000003,222.8} null

通過Pair中的order_id分組,因為Pair又是可比較,設(shè)置同一組按照amount降序排序。然后在Reduce端取第一個key-value對即可
Reduce端輸入k-v類似下表:

Key3 Value3
{order_0000001,[222.8,25.8]} null
{order_0000002,[522.8,122.4]} null
{order_0000003,[222.8]} null

以上是排序思路,因為這里比較簡單,直接在reduce端進行比較求最值更方便 【你可以自己試一下】


// 定義Pair對象
    public static class Pair implements WritableComparable<Pair> {
        private String order_id;
        private DoubleWritable amount;

        public Pair() {
            // TODO Auto-generated constructor stub
        }

        public Pair(String id, DoubleWritable amount) {
            this.order_id = id;
            this.amount = amount;
        }

        // 省略一些內(nèi)容,你可以直接去文件中看

        @Override
        public void write(DataOutput out) throws IOException {
            // TODO Auto-generated method stub
            out.writeUTF(order_id);
            out.writeDouble(amount.get());
        }

        @Override
        public void readFields(DataInput in) throws IOException {
            // TODO Auto-generated method stub
            order_id = in.readUTF();
            amount = new DoubleWritable(in.readDouble());
        }

        @Override
        public int compareTo(Pair o) {
            if (order_id.equals(o.order_id)) {// 同一order_id,按照amount降序排序
                return o.amount.compareTo(amount);
            } else {
                return order_id.compareTo(o.order_id);
            }
        }

    }
// 是分組不是分區(qū),分組是組內(nèi)定義一些規(guī)則由reduce去處理,分區(qū)是由多個Reduce處理,寫到不同文件中
// 自定義分組類
    public static class GroupComparator extends WritableComparator {
        public GroupComparator() {
            // TODO Auto-generated constructor stub
            super(Pair.class, true);
        }
        // Mapper端會對Pair排序,之后分組的規(guī)則是對Pair中的order_id比較
        @Override
        public int compare(WritableComparable a, WritableComparable b) {
            // TODO Auto-generated method stub
            Pair oa = (Pair) a;
            Pair ob = (Pair) b;
            return oa.getOrder_id().compareTo(ob.getOrder_id());
        }
    }
// Mapper類
    public static class MyMapper extends Mapper<Object, Text, Pair, NullWritable> {
        Pair pair = new Pair();

        public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
            String[] strs = value.toString().split(" ");
            pair.setOrder_id(strs[0]);
            pair.setAmount(new DoubleWritable(Double.parseDouble(strs[2])));
            context.write(pair, NullWritable.get());// 道理同上,以Pair作為key
            System.out.println(pair.getOrder_id()+","+pair.getAmount());
        }
    }
    
// Reducer類
    public static class MyReducer extends Reducer<Pair, NullWritable, Text, DoubleWritable> {
        public void reduce(Pair key, Iterable<NullWritable> values, Context context)
                throws IOException, InterruptedException {
            context.write(new Text(key.getOrder_id()), key.getAmount());// 已經(jīng)排好序的,取第一個即可
            System.out.println(key.order_id+": "+key.amount.get());
        }
    }

注意: main函數(shù)中要另外設(shè)置自定義的分組類 job.setGroupingComparatorClass(GroupComparator.class);

多文件輸入輸出、及不同輸入輸出格式化類型

6.合并多個小文件(多文件輸入輸出、及不同輸入輸出格式化類型)

需求分析

要計算的目標文件中有大量的小文件,會造成分配任務(wù)和資源的開銷比實際的計算開銷還大,這就產(chǎn)生了效率損耗。

需要先把一些小文件合并成一個大文件。

解答思路

簡單模型.jpg

如圖,MapReduce有一種簡單模型,僅僅只有Mapper。我想初學(xué)者都可能遇到過吧,當Mapper輸出k-v類型同Reducer輸入k-v不同類型時,Reducer不會執(zhí)行。

其次,是輸入和輸出數(shù)據(jù)如何格式化?

輸出很簡單,因為最后是合并成一個文件,直接以SequenceFileOutputFormat格式化類寫入即可

SequenceFileOutputFormat 的輸出是一個二進制順
序文件

輸入要自定義格式化類,具體過程可以參考我之前寫過的一篇文章:【MapReduce詳解及源碼解析(一)】——分片輸入、Mapper及Map端Shuffle過程 ,本來是需要實現(xiàn)InputFormat接口的getSplitscreateRecordReader方法,前者是邏輯上獲取切片,后者是將分片轉(zhuǎn)化為鍵值對形式。

但是這里我們是合并小文件,沒必要切片,直接將文件對象視為一個分片,鍵值對以文件名為key,文件對象為value。這里自定義MyInputFormat類繼承自InputFormat的實現(xiàn)類FileInputFormat類

public class MyInputFormat extends FileInputFormat<NullWritable, BytesWritable>{
    
    @Override
    protected boolean isSplitable(JobContext context, Path filename) {
        // TODO 因為是合并小文件,設(shè)置文件不可分割,k-v的v就是文件對象
        // 設(shè)置不可分,會跳過getSplits方法中切分邏輯
        return false;
    }

    @Override
    public RecordReader<NullWritable, BytesWritable> createRecordReader(InputSplit split, TaskAttemptContext context)
            throws IOException, InterruptedException {
        // TODO Auto-generated method stub
        MyRecordReader myRecordReader = new MyRecordReader();
//      myRecordReader
        return myRecordReader;
    }

}

然后,你在查看源碼時能夠發(fā)現(xiàn),createRecordReader方法返回值類型是RecordReader<KEYIN, VALUEIN>,該類型定義了如何獲取當前Key-value,如何生成Key-Value的三個核心方法getCurrentKey(),getCurrentValue(),nextKeyValue()

所以你又要定義自己的MyRecordReader類,其繼承的RecordReader類有initialize(初始化RecordReader)、getCurrentKey(),getCurrentValue(),nextKeyValue()close(關(guān)閉RecordReader)

具體的代碼你可以看我源碼文件

7.分組輸出到多個文件【多文件輸入輸出、及不同輸入輸出格式化類型】

需求分析

img

需要把相同訂單id的記錄放在一個文件中,并以訂單id命名。

8.join操作

需求分析

有2個數(shù)據(jù)文件:訂單數(shù)據(jù)、商品信息。【數(shù)據(jù)文件:product.txt,order.txt】

訂單數(shù)據(jù)表order

img

商品信息表product

img

需要用MapReduce程序來實現(xiàn)下面這個SQL查詢運算:

select o.id order_id, o.date, o.amount, p.id p_id, p.pname, p.category_id, p.price
from t_order o join t_product p on o.pid = p.id

9.計算出用戶間的共同好友

需求分析

下面是用戶的好友關(guān)系列表,每一行代表一個用戶和他的好友列表 【數(shù)據(jù)文件:friendsdata.txt】

image

需要求出哪些人兩兩之間有共同好友,及他倆的共同好友都有誰

例如從前2天記錄中可以看出,C、E是A、B的共同好友,最終的形式如下:

img

MapReduce理論基礎(chǔ)

Hadoop、Spark學(xué)習(xí)路線及資源收納

MapReduce書籍推薦

  • 《MapReduce Design Patterns》

    image
  • 《MapReduce2.0源碼分析與編程實戰(zhàn)》

    [圖片上傳失敗...(image-a2c861-1544427279895)]

  • 《Hadoop MapReduce v2 Cookbook, 2nd Edition》

image

MapReduce實戰(zhàn)系統(tǒng)學(xué)習(xí)流程

詞頻統(tǒng)計

數(shù)據(jù)去重

數(shù)據(jù)排序

求平均值、中位數(shù)、標準差、最大/小值、計數(shù)

分組、分區(qū)

數(shù)據(jù)輸入輸出格式化

多文件輸入、輸出

單表關(guān)聯(lián)

多表關(guān)聯(lián)

倒排索引

索引(index)作為一種具備各種優(yōu)勢的數(shù)據(jù)結(jié)構(gòu),被大量應(yīng)用在數(shù)據(jù)檢索領(lǐng)域

索引的優(yōu)點

  • 通過對關(guān)鍵字段排序,加快數(shù)據(jù)的檢索速度
  • 保證每一行數(shù)據(jù)的唯一性
index.png
reverseindex.jpg

需求

對于給定的文檔,確定每個單詞存在于某個文檔,同時在文檔中出現(xiàn)的次數(shù)

思路解答

  • Map端對文件統(tǒng)計每個單詞出現(xiàn)的次數(shù),輸出類似<{hadoop,file1},2>
  • Map端輸出前要先進行Combine過程,最終輸出類似< hadoop, file1:2>
  • Reduce端繼續(xù)對相同單詞進行合并,最終輸出類似<hadoop, file1:2 file2:5>

數(shù)據(jù)文件

隨便找?guī)灼⑽奈臋n就可以了

TopN

數(shù)據(jù)文件類似如下:

t001 2067
t002 2055
t003 109
t004 1200
t005 3368
t006 251
t001 3067
t002 255
t003 19
t004 2000
t005 368
t006 2512

隨便寫的,每一行以空格隔開,查找后面數(shù)之的TopN

思路解答

就是每一個Map Task任務(wù)要求其只輸出TopN數(shù)據(jù),這里借助TreeMap自動排序的特性【 將數(shù)字作為排序鍵 】,保證TopN。然后是Reduce中再次求解TopN即可

注意: 在main函數(shù)中要設(shè)置ReduceTask數(shù)量為1,保證最終的TopN

// Mapper中實現(xiàn)的map方法如下
    private TreeMap<Integer, Text> visittimesMap = new TreeMap<Integer, Text>();    //TreeMap是有序KV集合

    @Override
    public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
        if (value == null) {
            return;
        }
        String[] strs = value.toString().split(" ");
        String tId = strs[0];
        String tVisittimes = strs[1];
        if (tId == null || tVisittimes == null) {
            return;
        }
        visittimesMap.put(Integer.parseInt(tVisittimes), new Text(value));  //將訪問次數(shù)(KEY)和行記錄(VALUE)放入TreeMap中自動排序
        if (visittimesMap.size() > 5) { //如果TreeMap中元素超過N個,將第一個(KEY最小的)元素刪除
            visittimesMap.remove(visittimesMap.firstKey());
        }
    }
    @Override
    protected void cleanup(Context context) throws IOException, InterruptedException {
        for (Text t : visittimesMap.values()) {
            context.write(NullWritable.get(), t);   //在clean()中完成Map輸出
        }
    }

PeopleRank算法實現(xiàn)

推薦系統(tǒng)——協(xié)同過濾算法實現(xiàn)

數(shù)據(jù)

見resources文件夾下,其中rand.sh腳本用于生成隨機日期數(shù)據(jù)

關(guān)于我

你可以在途徑找到我

本文為項目中說明文檔,首發(fā)于 https://blog.csdn.net/lzw2016/article/details/84928495

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 32,039評論 2 89
  • 當在電話里對著你,訴說我在大學(xué)的孤獨,不開心時,電話那邊的你,沉默好久,終于說一句,下雪了嗎?我遲鈍了一下,你...
    離洛殊閱讀 204評論 0 1
  • 夜的侵蝕給予我 逃生的靈感 飄霧的月色光影 漸漸染著在 寂寞的圍墻 路燈唏噓般的歌唱 一曲冗長的音樂 交錯著風(fēng)的死...
    渡落劫閱讀 165評論 0 0
  • 釣魚舟閱讀 596評論 2 6