取出前三名的值
简单排序:直接通过Reducer端中的reduce方法可以直接进行排序 Mapper端中也会进行排序 Mapper端中也会进行排序(以key进行排排序)
内存排序:将数据存储到一个容器中(集合),这个集合是存储在内存中,
对集合中的数据进行排序称之为内存排序
第一种:
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
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.output.FileOutputFormat;
/**
* Description: TOPN<br/>
* Copyright (c) , 2018, xlj <br/>
* This program is protected by copyright laws. <br/>
* Program Name:TOPN.java <br/>
*
* @version : 1.0
*/
public class TOPN {
//实现MapReduce
/*1.实现Mapper端的逻辑
* KEYIN:文件中读取的偏移量-->LongWritable(固定的)
* VALUEIN:文件中实际读取的内容-->Text
* KEYOUT:Mapper处理完成后传递给Reducer中的KEYIN的数据类型-->不固定,根据需求来
* VALUEOUT:Mapper端处理完成后传递给Reducer中的VALUEIN的数据类型-->不固定,根据需求来
*/
public static class MyMapper extends Mapper<LongWritable, Text, Text, Text>{
/* 进入Map处理逻辑之前会执行一次的方法
*/
@Override
protected void setup(Context context)
throws IOException, InterruptedException {
}
/*
* 需要实现Mapper端的处理逻辑
* key:是文件中数据的偏移量,数据类型是由泛型中定义得来的KEYIN
* value:是文件中实际的内容,数据类型是泛型中定义得来的VALUEIN
* context:将处理过后产生的KV,写成文件输出
*/
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String []words = value.toString().split(" ");
for (String word : words) {
context.write(new Text(word), new Text("1"));
}
}
/*
* 在Map处理逻辑之后会执行一次,可以处理一些逻辑
*/
@Override
protected void cleanup(Context context)
throws IOException, InterruptedException {
}
}
//实现Reducer端的逻辑
/*
* Reducer相当于对Mapper端处理过后的数据进行一个实际的处理业务
* KEYIN-->Mapper处理过后输出key的数据类型,由Mapper的泛型中第三个参数决定
* VALUE-->Mapper处理过后输出value的数据类型,由Mapper的泛型中第四个参数决定
* KEYOUT-->Reducer端处理完数据之后要写出key的数据类
* VALUEOUT-->Reducer处理完数据之后,要写出value的 数据类
*/
public static class MyReduce extends Reducer<Text, Text, Text, Text>{
Map<String,Integer> map = new HashMap<>();
/* 执行Reducer端先执行一次的方法
*/
@Override
protected void setup(Context context) throws IOException, InterruptedException {
}
/*
*reduce方法是处理业务的核心逻辑
*key: 是从Mapper端处理完成后,产生key的数据
*values-->是从 Mapper端处理完成之后相同key的values的数据集合
*context-->写出实际 处理完成后KV的方法
*/
@Override
protected void reduce(Text key, Iterable<Text> values, Context context)
throws IOException, InterruptedException {
int count = 0;
//<hello,list<1,1>
for (Text t : values) {
count += Integer.parseInt(t.toString());
}
/*
* 因为当前单词统计的个数存储到一个集合中进行排序
* 因为集合中的单词要排序,实体排序是统计单词个数的值,那么
* 单纯的存储单词个数是无法得知是哪个单词
* 所以我们将单词定义在reduce方法 中,那么集合只能在方法体中使用,然后排序输出
* 若将集合定义在reduce方法中,那么集合只能在方法体中使用,然后排序输出
* 若将集合声明为成员变量,那么在其他地方就可以使用,
* 那么此时可以用cleanup来做最终输出
*/
map.put(key.toString(), count);
}
/*
* 执行完reduce方法执行的方法
*
*/
@Override
protected void cleanup(Context context)
throws IOException, InterruptedException {
Set<Map.Entry<String, Integer>> mapSet = map.entrySet();
List<Map.Entry<String, Integer>> list = new ArrayList<>(mapSet);
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
return o2.getValue().compareTo(o1.getValue()) ;
}
});
int i = 0;//作为一个计数器
Iterator<Entry<String, Integer>> it = list.iterator();
while(it.hasNext()) {
if(i == 3) {//当 i = 3时,则停止输出
break;
}
Map.Entry<String, Integer> words= it.next();
String key = words.getKey();
Integer value = words.getValue();
context.write(new Text(key+""), new Text(value+""));
i++;
}
}
}
/**
* 实现job,完成作业配置
*/
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//1.获取配置对象
Configuration conf = new Configuration();
//2.创建Job对象(创建作业)
/*
* 这个方法一共有两个参数版本
* getInstance(conf) --------直接传入配置对象
* getInstance(conf,"WordCountCombiner")---传入配置对象和类的名字
*/
Job job = Job.getInstance(conf);
//3.设置运行job的class
job.setJarByClass(TOPN.class);
//4.设置Mapper端的运行类和输出key,输出value的数据类型
job.setMapperClass(MyMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//5.读取数据来源
//这两个方法处理是一样的,只是最后一个参数不同,
/*
* FileInputFormat.addInputPath(job, new Path("input/data1"));
*
* add:证明只有 一个路径,
*
* FileInputFormat.setInputPaths(job, new Path("input/data1"));
* set证明后面是可变参数,多个
*
* 因为当前运行的是本地MR,所以数据是 从本地读取,若需要在集群中运行,这个位置的参数应该是args[0]
*/
//FileInputFormat.addInputPath(job, new Path("input/data2"));
FileInputFormat.setInputPaths(job, new Path("input/data3"));
//优化设置
//一般可以写分区设置,多文件输出设置,Combiner设置
/*
* 并不是所有job都适用于Combiner,只有操作满足结合规律才可以进行设置
* 如 求和,求最大值,topN 等可以使用Combiner
*
* Combiner不一定需要存在,只有数据量较大,需要做优化的时候可以使用
*/
//6.社会Reducer端的运行类和输出key和输出value的数据类型
job.setReducerClass(MyReduce.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//7.处理完文件之后输出的路径
//ps:因为当前运行的是本地MR,所以数据是写到本地的,若需要再集群中运行,这个位置的参数应该是args[1]
//数据是存储到HDFS中
FileOutputFormat.setOutputPath(job, new Path("output1"));
//8.提交作业
int isok = job.waitForCompletion(true)?0:-1;
System.exit(isok);
}
}
第二种:
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
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.output.FileOutputFormat;
/**
* Description: TOPN<br/>
* Copyright (c) , 2018, xlj <br/>
* This program is protected by copyright laws. <br/>
* Program Name:TOPN.java <br/>
*
* @version : 1.0
*/
public class TOPN2 {
//实现MapReduce
/*1.实现Mapper端的逻辑
* KEYIN:文件中读取的偏移量-->LongWritable(固定的)
* VALUEIN:文件中实际读取的内容-->Text
* KEYOUT:Mapper处理完成后传递给Reducer中的KEYIN的数据类型-->不固定,根据需求来
* VALUEOUT:Mapper端处理完成后传递给Reducer中的VALUEIN的数据类型-->不固定,根据需求来
*/
public static class MyMapper extends Mapper<LongWritable, Text, Text, Text>{
/* 进入Map处理逻辑之前会执行一次的方法
*/
@Override
protected void setup(Context context)
throws IOException, InterruptedException {
}
/*
* 需要实现Mapper端的处理逻辑
* key:是文件中数据的偏移量,数据类型是由泛型中定义得来的KEYIN
* value:是文件中实际的内容,数据类型是泛型中定义得来的VALUEIN
* context:将处理过后产生的KV,写成文件输出
*/
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String []words = value.toString().split(" ");
for (String word : words) {
context.write(new Text(word), new Text("1"));
}
}
/*
* 在Map处理逻辑之后会执行一次,可以处理一些逻辑
*/
@Override
protected void cleanup(Context context)
throws IOException, InterruptedException {
}
}
//实现Reducer端的逻辑
/*
* Reducer相当于对Mapper端处理过后的数据进行一个实际的处理业务
* KEYIN-->Mapper处理过后输出key的数据类型,由Mapper的泛型中第三个参数决定
* VALUE-->Mapper处理过后输出value的数据类型,由Mapper的泛型中第四个参数决定
* KEYOUT-->Reducer端处理完数据之后要写出key的数据类
* VALUEOUT-->Reducer处理完数据之后,要写出value的 数据类
*/
public static class MyReduce extends Reducer<Text, Text, Text, Text>{
List<String> list = new ArrayList<>();
/* 执行Reducer端先执行一次的方法
*/
@Override
protected void setup(Context context) throws IOException, InterruptedException {
}
/*
*reduce方法是处理业务的核心逻辑
*key: 是从Mapper端处理完成后,产生key的数据
*values-->是从 Mapper端处理完成之后相同key的values的数据集合
*context-->写出实际 处理完成后KV的方法
*/
@Override
protected void reduce(Text key, Iterable<Text> values, Context context)
throws IOException, InterruptedException {
int count = 0;
//<hello,list<1,1>
for (Text t : values) {
count += Integer.parseInt(t.toString());
}
/*
* 因为当前单词统计的个数存储到一个集合中进行排序
* 因为集合中的单词要排序,实体排序是统计单词个数的值,那么
* 单纯的存储单词个数是无法得知是哪个单词
* 所以我们将单词定义在reduce方法 中,那么集合只能在方法体中使用,然后排序输出
* 若将集合定义在reduce方法中,那么集合只能在方法体中使用,然后排序输出
* 若将集合声明为成员变量,那么在其他地方就可以使用,
* 那么此时可以用cleanup来做最终输出
*/
list.add(key.toString()+"_"+count);
}
/*
* 执行完reduce方法执行的方法
*
*/
@Override
protected void cleanup(Context context)
throws IOException, InterruptedException {
//对集合中的数进行排序
for (int i = 0; i < list.size(); i++) {
for (int j = i+1; j < list.size(); j++) {
/*
* 当前集合中是字符串
* 不能 使用字符串和字符串进行比较
* 所以这里需要将字符串进行一个拆分,取出后面的值进行比较
* 这样一来就能知道谁最大了,取出后面的值进行比较
* 降序排序 从达到小
*/
if(Integer.parseInt(list.get(i).split("_")[1]) < Integer.parseInt(list.get(j).split("_")[1])) {
String tmp = "";
tmp = list.get(i);
list.set(i, list.get(j));
list.set(j, tmp);
}
}
}
for (int i = 0; i < 3; i++) {
String [] str = list.get(i).split("_");
context.write(new Text(str[0]), new Text(str[1]));
}
}
}
/**
* 实现job,完成作业配置
*/
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//1.获取配置对象
Configuration conf = new Configuration();
//2.创建Job对象(创建作业)
/*
* 这个方法一共有两个参数版本
* getInstance(conf) --------直接传入配置对象
* getInstance(conf,"WordCountCombiner")---传入配置对象和类的名字
*/
Job job = Job.getInstance(conf);
//3.设置运行job的class
job.setJarByClass(TOPN2.class);
//4.设置Mapper端的运行类和输出key,输出value的数据类型
job.setMapperClass(MyMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//5.读取数据来源
//这两个方法处理是一样的,只是最后一个参数不同,
/*
* FileInputFormat.addInputPath(job, new Path("input/data1"));
*
* add:证明只有 一个路径,
*
* FileInputFormat.setInputPaths(job, new Path("input/data1"));
* set证明后面是可变参数,多个
*
* 因为当前运行的是本地MR,所以数据是 从本地读取,若需要在集群中运行,这个位置的参数应该是args[0]
*/
//FileInputFormat.addInputPath(job, new Path("input/data2"));
FileInputFormat.setInputPaths(job, new Path("input/data3"));
//优化设置
//一般可以写分区设置,多文件输出设置,Combiner设置
/*
* 并不是所有job都适用于Combiner,只有操作满足结合规律才可以进行设置
* 如 求和,求最大值,topN 等可以使用Combiner
*
* Combiner不一定需要存在,只有数据量较大,需要做优化的时候可以使用
*/
//6.社会Reducer端的运行类和输出key和输出value的数据类型
job.setReducerClass(MyReduce.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//7.处理完文件之后输出的路径
//ps:因为当前运行的是本地MR,所以数据是写到本地的,若需要再集群中运行,这个位置的参数应该是args[1]
//数据是存储到HDFS中
FileOutputFormat.setOutputPath(job, new Path("output1"));
//8.提交作业
int isok = job.waitForCompletion(true)?0:-1;
System.exit(isok);
}
file.txt
hello xiaoming hello xiaoming xiaoming is best xiaoming better
hadoop is good
spark is nice