前言
這里放一個我學(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)容實例:
要把同一個用戶的上行流量、下行流量進行累加,并計算出綜合 。例如上面的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é)生按成績排序,手機用戶流量按上行流量升序,下行流量降序排序等等
日期計數(shù)升序排序
-
日期計數(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
- 手機用戶流量按上行流量升序,下行流量降序排序
解答思路
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ù):
需要求出每一個訂單中成交金額最大的一筆交易。
思路解答
實際上是求最大值、最小值的問題,一拿到題,大概會冒出兩種思路吧
- 先排序(升序),Reduce端取第一條就是最小值,最后一條是最大值
- 不排序,在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)生了效率損耗。
需要先把一些小文件合并成一個大文件。
解答思路
如圖,MapReduce有一種簡單模型,僅僅只有Mapper。我想初學(xué)者都可能遇到過吧,當Mapper輸出k-v類型同Reducer輸入k-v不同類型時,Reducer不會執(zhí)行。
其次,是輸入和輸出數(shù)據(jù)如何格式化?
輸出很簡單,因為最后是合并成一個文件,直接以SequenceFileOutputFormat格式化類寫入即可
SequenceFileOutputFormat 的輸出是一個二進制順
序文件
輸入要自定義格式化類,具體過程可以參考我之前寫過的一篇文章:【MapReduce詳解及源碼解析(一)】——分片輸入、Mapper及Map端Shuffle過程 ,本來是需要實現(xiàn)InputFormat
接口的getSplits
和 createRecordReader
方法,前者是邏輯上獲取切片,后者是將分片轉(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.分組輸出到多個文件【多文件輸入輸出、及不同輸入輸出格式化類型】
需求分析
需要把相同訂單id的記錄放在一個文件中,并以訂單id命名。
8.join操作
需求分析
有2個數(shù)據(jù)文件:訂單數(shù)據(jù)、商品信息。【數(shù)據(jù)文件:product.txt,order.txt】
訂單數(shù)據(jù)表order
商品信息表product
需要用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】
需要求出哪些人兩兩之間有共同好友,及他倆的共同好友都有誰。
例如從前2天記錄中可以看出,C、E是A、B的共同好友,最終的形式如下:
MapReduce理論基礎(chǔ)
- 大數(shù)據(jù)之Hadoop學(xué)習(xí)(環(huán)境配置)——Hadoop偽分布式集群搭建
- MapReduce編程初步及WordCount源碼分析
- 【MapReduce詳解及源碼解析(一)】——分片輸入、Mapper及Map端Shuffle過程
- [MapReduce:詳解Shuffle過程]
Hadoop、Spark學(xué)習(xí)路線及資源收納
- [Hadoop及Spark學(xué)習(xí)路線及資源收納 ]
- 大數(shù)據(jù)之Hadoop學(xué)習(xí)《一》——認識HDFS
- 調(diào)用JAVA API 對 HDFS 進行文件的讀取、寫入、上傳、下載、刪除等操作
MapReduce書籍推薦
-
《MapReduce Design Patterns》
image
-
《MapReduce2.0源碼分析與編程實戰(zhàn)》
[圖片上傳失敗...(image-a2c861-1544427279895)]
《Hadoop MapReduce v2 Cookbook, 2nd Edition》
MapReduce實戰(zhàn)系統(tǒng)學(xué)習(xí)流程
詞頻統(tǒng)計
數(shù)據(jù)去重
數(shù)據(jù)排序
求平均值、中位數(shù)、標準差、最大/小值、計數(shù)
分組、分區(qū)
數(shù)據(jù)輸入輸出格式化
- 【源碼 InputOutputFormatTest】,這個是輸入不同路徑下的CSV、TXT文件并分區(qū)輸出到不同文件中
- 【源碼 inputformat】
多文件輸入、輸出
單表關(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ù)的唯一性
需求
對于給定的文檔,確定每個單詞存在于某個文檔,同時在文檔中出現(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