目录
核心思想
MapReduce是一个分布式运算程序的编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架。其核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个Hadoop集群上。
分布式的运算程序往往需要分成至少2个阶段。
- 第一个阶段的MapTask并发实例,完全并行运行,互不相干。
- 第二个阶段的ReduceTask并发实例互不相干,但是他们的数据依赖于上一个阶段的所有MapTask并发实例的输出。
MapReduce编程模型只能包含一个Map阶段和一个Reduce阶段,如果用户的业务逻辑非常复杂,那就只能多个MapReduce程序,串行运行。
Mapper
- 用户自定义的Mapper要继承自己的父类
- Mapper的输入数据是KV对的形式(KV的类型可自定义)
- Mapper中的业务逻辑写在map()方法中
- Mapper的输出数据是KV对的形式(KV的类型可自定义)
- map()方法(MapTask进程)对每一个<K,V>调用一次
Reducer
- 用户自定义的Reducer要继承自己的父类
- Reducer的输入数据类型对应Mapper的输出数据类型,也是KV
- Reducer的业务逻辑写在Reducer()方法中
- ReduceTask进程对每一组相k的<k,v>组调用一次reduce()方法
Driver
相当于YARN集群的客户端,用于提交我们整个程序到YARN集群,提交的是封装了MapReduce程序相关运行参数的job对象。
数据类型
简单案例wordcount
需求:使用mapreduce统计分析以下城市出现的次数
maven工程搭建
maven工程搭建我们在上一节的笔记中已经详细介绍,大家可以参考:大数据实训笔记3:hdfs
我们在这里只介绍pom.xml中导入的依赖和log4j.properties日志的内容。
<!-- 自定义属性设置版本号 -->
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<hadoop-version>3.1.3</hadoop-version>
</properties>
<dependencies>
<!--junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<!--slf4j日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>2.0.0-alpha2</version>
</dependency>
<!--hadoop中的common工具类-->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>${hadoop-version}</version>
</dependency>
<!--hadoop中的client-->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop-version}</version>
</dependency>
<!--hadoop中的hdfs-->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>${hadoop-version}</version>
</dependency>
</dependencies>
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
之后我们创建wordcount包,在这个包下面写Mapper, Reducer和Driver类。
代码实现
代码中有详细的注释,在此不再赘述。后续的mapreduce案例均按照此框架进行代码的编写,大家一定要借助这个简单的案例理解透彻。注意复制代码之后导入的包要正确!
//Mapper类
//<>中的数据类型分别为输入key类型、输入value类型、输出key类型、输入value类型
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private Text k=new Text(); //输出key
private IntWritable v=new IntWritable(1); //输出value,这里设置它恒为1
//重载map方法,只要输入map后回车就可以自动生成方法框架
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString(); //获取一行数据
String[] split = line.split(" "); //切割,以空格为分隔符
for (String word : split) {
k.set(word); //设置输出的key为每一个单词
context.write(k,v); //写出,例如(北京, 1), (上海, 1)
}
}
}
//Reducer类
//<>中的数据类型分别为:
//输入key类型(Mapper输出key类型)、输入value类型(Mapper输出value类型)
//输出key类型(最终输出key类型)、输出value类型(最终输出value类型)
public class WordCountReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
private IntWritable v=new IntWritable(); //输出value
//重载reduce方法
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum=0; //每一个城市出现的次数
for (IntWritable value : values) { //求和,算出一个城市出现了几次
sum+=value.get();
}
v.set(sum); //输出value为每一个城市出现的次数
context.write(key,v); //写出
}
}
//Driver类,用来调度Mapper和Reducer类,最终执行的就是这个类的main方法
public class WordCountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//设置输入输出路径
//要分析的txt放在input\wordcount文件夹下
//输出路径wordcount是不存在的,执行main方法之后自动创建,如果该文件夹已经存在会报错
args=new String[]{"D:\\input\\wordcount","D:\\output\\wordcount"};
//写一个配置对象
Configuration conf=new Configuration();
//获取job对象
Job job = Job.getInstance(conf);
//设置jar位置
job.setJarByClass(WordCountDriver.class);
//关联mapper和reducer
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
//设置mapper的输出类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//设置最终输出类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//设置输入输出路径
FileInputFormat.setInputPaths(job,new Path(args[0]));
FileOutputFormat.setOutputPath(job,new Path(args[1]));
//提交
boolean result = job.waitForCompletion(true);
System.exit(result?0:1);
}
}
执行Driver类中的main方法,我们来到输出路径下,就可以查看输出结果了。 如果输出的中文为乱码,把txt文档的字符编码改为utf-8即可。
在集群中测试
除了在本地运行,我们还可以把刚刚编写的程序打包上传到集群中测试。打包之前,记得把自定义的路径(args)注释掉,否则会报错。
首先,我们需要在pom.xml中加入打包插件。其中的com.mr.wordcount.WordCountDriver是我的Driver类所在位置,大家可以选中自己的Driver类,右键-Copy Reference获取。
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin </artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.mr.wordcount.WordCountDriver</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
然后,点击右边栏的Maven-mapreduce_demo(项目名称)-Lifecycle,依次运行clean, install, package,就可以在项目下的target文件夹找到我们打包好的jar包了。
将其重命名后,上传到集群测试。Alt+P跳转到上传界面。
sftp> cd /opt/module/hadoop-3.1.3
拖拽上传即可。上传成功后,我们就可以测试并查看结果。
//com.mr.wordcount.WordCountDriver是Driver类的位置,记得更改
//将要统计分析的txt放在input文件夹下
//output文件夹必须不存在,执行后自动生成
[hadoop@hadoop101 hadoop-3.1.3]$ hadoop jar wordcount.jar com.mr.wordcount.WordCountDriver /input /output
之后还会介绍很多案例,也可以用同样的方法打包、上传、测试。
序列化
序列化和反序列化
序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储到磁盘(持久化)和网络传输。
反序列化就是将收到字节序列(或其他数据传输协议)或者是磁盘的持久化数据,转换成内存中的对象。
一般来说,“活的”对象只生存在内存里,关机断电就没有了。而且“活的”对象只能由本地的进程使用,不能被发送到网络上的另外一台计算机。然而序列化可以存储”活的”对象,可以将”活的”对象发送到远程计算机。
Java的序列化是一个重量级序列化框架(Serializable),一个对象被序列化后,会附带很多额外的信息(各种校验信息,Header,继承体系等),不便于在网络中高效传输。所以,Hadoop自己开发了一套序列化机制(Writable)。
自定义序列化
- 写一个类,必须实现Writable接口
- 重写序列化方法
- 重写反序列化方法
- 写空参构造方法
- 重写toString()
案例1:计算
需求:对以下学生的语文、数学、英语成绩求每位学生的最高分、最低分、总分、平均分。
//自定义序列化
public class StudentBean implements Writable {
//对应txt中的几个变量和我们需要求的变量
private String name;
private long chinese;
private long math;
private long english;
private long maxScore;
private long minScore;
private long sumScore;
private double avgScore;
//序列化
@Override
public void write(DataOutput out) throws IOException {
out.writeUTF(name);
out.writeLong(chinese);
out.writeLong(math);
out.writeLong(english);
out.writeLong(maxScore);
out.writeLong(minScore);
out.writeLong(sumScore);
out.writeDouble(avgScore);
}
//反序列化
@Override
public void readFields(DataInput in) throws IOException {
name = in.readUTF();
chinese = in.readLong();
math = in.readLong();
english = in.readLong();
maxScore = in.readLong();
minScore = in.readLong();
sumScore = in.readLong();
avgScore = in.readDouble();
}
//空参构造
public StudentBean() {
}
//有参构造
public StudentBean(String name, Long chinese, Long math, Long english) {
this.name = name;
this.chinese = chinese;
this.math = math;
this.english = english;
this.maxScore = Math.max(Math.max(chinese, math), english);
this.minScore = Math.min(Math.min(chinese, math), english);
this.sumScore = (chinese + math + english);
this.avgScore = this.sumScore / 3.0;
}
//以下这些右键-Generate-Getter & Setter就可以自动生成
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getChinese() {
return chinese;
}
public void setChinese(Long chinese) {
this.chinese = chinese;
}
public Long getMath() {
return math;
}
public void setMath(Long math) {
this.math = math;
}
public Long getEnglish() {
return english;
}
public void setEnglish(Long english) {
this.english = english;
}
public Long getMaxScore() {
return maxScore;
}
public void setMaxScore(Long maxScore) {
this.maxScore = maxScore;
}
public Long getMinScore() {
return minScore;
}
public void setMinScore(Long minScore) {
this.minScore = minScore;
}
public Long getSumScore() {
return sumScore;
}
public void setSumScore(Long sumScore) {
this.sumScore = sumScore;
}
public Double getAvgScore() {
return avgScore;
}
public void setAvgScore(Double avgScore) {
this.avgScore = avgScore;
}
//重载toString方法,后续Mapper和Reducer会用到
//不输出name是因为后续我们用name做key,输出的时候已经有name了,不需要重复输出
@Override
public String toString() {
return chinese +
"\t" + math +
"\t" + english +
"\t" + maxScore +
"\t" + minScore +
"\t" + sumScore +
"\t" + avgScore ;
}
}
接下来编写Mapper, Reducer和Driver类,与上个案例类似,这里只给出部分代码。
//Mapper类
public class StudentMapper extends Mapper<LongWritable, Text, Text, StudentBean> {
private Text k = new Text();
private StudentBean v = new StudentBean();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
//这是分割的另一种方法,可以按照\t,\n,空格等进行分割
StringTokenizer sti = new StringTokenizer(line);
v.setName(sti.nextToken());
v.setChinese(Long.parseLong(sti.nextToken()));
v.setMath(Long.parseLong(sti.nextToken()));
v.setEnglish(Long.parseLong(sti.nextToken()));
k.set(v.getName());
context.write(k, v);
}
}
//Reducer类
public class StudentReducer extends Reducer<Text, StudentBean, Text, StudentBean> {
@Override
protected void reduce(Text key, Iterable<StudentBean> values, Context context) throws IOException, InterruptedException {
StudentBean bean = values.iterator().next();
//计算在这步完成,我们编写的有参构造函数会帮我们完成计算
StudentBean v = new StudentBean(bean.getName(), bean.getChinese(), bean.getMath(), bean.getEnglish());
context.write(key, v);
}
}
运行结果如下:
案例2:过滤
需求:根据日志信息,统计每个联系方式上网流量,上行流量、下行流量、总流量。
这个案例与案例1类似,大家可以自行编写。不过观察数据,我们会发现,有一些数据为空。因此,在Mapper的编写中,要格外注意。
//在Mapper类中,获取上行流量与下行流量时
//因为不一定有ip地址,因此不能直接写split[3]
v.setUpFlow(Long.parseLong(split[split.length - 3]));
v.setDownFlow(Long.parseLong(split[split.length - 2]));
//Reducer类
public class FlowReducer extends Reducer<Text, FlowBean, Text, FlowBean> {
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
long sumUpFlow = 0;
long sumDownFlow = 0;
//对电话号码相同的用户,计算出其上下行流量的总和
for (FlowBean bean : values) {
sumUpFlow += bean.getUpFlow();
sumDownFlow += bean.getDownFlow();
}
FlowBean v = new FlowBean(key.toString(), sumUpFlow, sumDownFlow);
context.write(key, v);
}
}
运行结果如下:
案例3:排序
需求:将数据先按年龄升序排序,如果年龄相等,再比较身高。
和前面的案例相似,只是需要多写一个MySort类负责比较。
//MySort类
public class MySort implements WritableComparable<MySort2> {
private int age;
private int height;
//相等返回0,小于返回-1,大于返回1
@Override
public int compareTo(MySort2 other) {
if(this.age == other.age) {
if(this.height == other.height) {
return 0;
}
else if(this.height < other.height) {
return -1;
}
else {
return 1;
}
}
else if(this.age < other.age) {
return -1;
}
else {
return 1;
}
}
//序列化
@Override
public void write(DataOutput out) throws IOException {
out.writeInt(age);
out.writeInt(height);
}
//反序列化
@Override
public void readFields(DataInput in) throws IOException {
age = in.readInt();
height = in.readInt();
}
public MySort() {
}
public MySort(int age, int height) {
this.age = age;
this.height = height;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
@Override
public String toString() {
return age + height + "";
}
@Override
public int hashCode() {
return age + height;
}
}
//Mapper类中,将输出类型设置为MySort
private MySort k = new MySort();
k.setAge(v.getAge());
k.setHeight(v.getHeight());
案例4:分区
需求:在案例3的基础上,将数据按照年龄分为3个区。
//MyPartioner自定义分区
//泛型参数是mapper输出数据的key和value的数据类型
public class MyPartioner extends Partitioner<MySort, WorkSort> {
@Override
public int getPartition(MySort key, WorkSort value, int num) {
//按照年龄分区
//num代表在年龄范围内平均分成几个区
return value.getAge()/(100/num);
}
}
//在Driver类中
//设置分区
job.setPartitionerClass(MyPartioner.class);
job.setNumReduceTasks(3);
案例5:组合
需求:在案例3的基础上,统计各组中身高超过170的附近人数。
//MyGroupingComparator类
public class MyGroupingComparator extends WritableComparator {
@Override
public int compare(WritableComparable a, WritableComparable b) {
//key1,key2是mapper输出的key
MySort2 key1 = (MySort2) a;
MySort2 key2 = (MySort2) b;
//身高在170以上的返回0,可以把key所对应的value组合放到同一key的values中
if(key1.getHeight() > 170 && key2.getHeight() > 170)
return 0;
else
return -1;
}
public MyGroupingComparator() {
//创建适合key的实例
super(MySort2.class, true);
}
}
//在Driver类中
//设置看哪些数据可以在reducer端进行一次处理
job.setGroupingComparatorClass(MyGroupingComparator.class);
job.setNumReduceTasks(1);
案例6:Join
需求:统计person表和score表有关联的数据信息。(score表第二列是person表的第一列id)
//Record类
public class Record implements Writable {
private int isPerson; //0表示score, 1表示person
private String content; //输出内容
//......
}
除了像上述几个案例那样将Mapper, Reducer和Driver类分开编写,我们还可以把它们作为一个类下的子类。
//MyJoin类
public class MyJoin {
//Mapper类
public static class JoinMapper extends Mapper<LongWritable, Text, IntWritable, Record> {
private IntWritable k = new IntWritable(); //存放personId
private Record v = new Record(); //存放输出内容
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String [] fields = value.toString().split("\t"); //获取一行数据,以tab为分隔符
int personId = 0;
if(fields.length == 6) { //如果长度为6,说明为person表
personId = Integer.parseInt(fields[0]); //person表的第一列为personId
v.setIsPerson(1);
v.setContent(fields[1] + "\t" + fields[2] + "\t" + fields[3] + "\t" + fields[4] + "\t" + fields[5]);
}
else { //否则是score表
personId = Integer.parseInt(fields[1]); //score表的第二列为personId
v.setIsPerson(0);
v.setContent(fields[2] + "\t" + fields[3]);
}
k.set(personId);
context.write(k, v);
}
}
//Reducer类
public static class JoinReducer extends Reducer<IntWritable, Record, IntWritable, Text> {
private Text v = new Text();
@Override
protected void reduce(IntWritable key, Iterable<Record> values, Context context) throws IOException, InterruptedException {
String personStr = null;
List<String> scoreList = new ArrayList(); //一个人可能有多门课的成绩
for (Record record: values) {
if (record.getIsPerson() == 1){
personStr = record.getContent();
}
else {
scoreList.add(record.getContent());
}
}
for (String score: scoreList) {
v.set(personStr + "\t" + score);
context.write(key, v);
}
}
}
//相当于Driver类
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//......
Job job = Job.getInstance(conf, "queryMr");
//......
}
}
mapreduce和mysql
环境准备
自行安装mysql,可以安装Navicat,便于数据库操作。
在mysql中,新建mapreduce数据库。在mapreduce数据库下,新建tb_student表。设计如下:
插入几条数据:
在pom.xml中导入mysql依赖。注意替换成自己的版本。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
案例1:从mysql中读数据
需求:统计学生信息中每个年龄的学生数量。
//Student类,注意一定要DBWritable类
public class Student implements DBWritable, Writable {
//存放从mysql中读的数据
private int sid;
private String sname;
private String sex;
private int age;
private String birthday;
//序列化和反序列化的代码自行编写
//从数据库中读取数据
@Override
public void readFields(ResultSet resultSet) throws SQLException {
this.sid = resultSet.getInt("sid");
this.sname = resultSet.getString("sname");
this.sex = resultSet.getString("sex");
this.age = resultSet.getInt("age");
this.birthday = resultSet.getString("birthday");
}
//其他方法自行编写
}
//ReadSql类,负责从MySql中读取数据
public class ReadSql {
//Mapper类
public static class ReadDBMapper extends Mapper<LongWritable, Student, IntWritable, IntWritable> {
private IntWritable k = new IntWritable();
private IntWritable v = new IntWritable(1); //默认为1
@Override
protected void map(LongWritable key, Student value, Context context) throws IOException, InterruptedException {
k.set(value.getAge()); //按照需求,以年龄为key
context.write(k, v);
}
}
//Reducer类
public static class ReadDBReducer extends Reducer<IntWritable, IntWritable, Text, NullWritable> {
private Text k = new Text();
@Override
protected void reduce(IntWritable key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int count = 0;
for (IntWritable value: values) {
count += value.get();
}
k.set(key.get() + "岁的学生有" + count + "人");
context.write(k, NullWritable.get());
}
}
//相当于Driver类
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
//连接数据库
//localhost连接本地数据库,3306为端口,字符集为utf-8,时区为GMT(这一项不设置我会报错,大家可以自行调整)
//最后两个参数是用户名和密码
DBConfiguration.configureDB(conf, "com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/mapreduce?characterEncoding=utf-8&serverTimezone=GMT", "root", "passwd");
Job job = Job.getInstance(conf, "mrsql");
//......
//表中的属性名,不能出错,否则无法读取数据
String[] fields = {"sid,", "sname", "sex", "age", "birthday"};
//设置输入输出路径
DBInputFormat.setInput(job, Student.class, "tb_student", null, "sid", fields);
Path outPath = new Path("D:\\output\\sql");
FileSystem fs = outPath.getFileSystem(conf);
//如果输出路径存在,删除
if(fs.exists(outPath)) {
fs.delete(outPath, true);
}
FileOutputFormat.setOutputPath(job, outPath);
//......
}
}
案例2:往mysql中写数据
需求:把文件中数据写入到mysql中。
//在Student类中
//写入数据库
@Override
public void write(PreparedStatement statement) throws SQLException {
statement.setInt(1, sid);
statement.setString(2, sname);
statement.setString(3, sex);
statement.setInt(4, age);
statement.setString(5, birthday);
}
//WriteSql类,负责往MySql中写入数据
public class WriteSql {
//Mapper类和Reducer类自行编写
//相当于Driver类
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//......
//设置输入输出路径
FileInputFormat.setInputPaths(job, new Path("D:\\input\\sql"));
String[] fields = {"sid", "sname", "sex", "age", "birthday"};
DBOutputFormat.setOutput(job, "tb_student", fields);
//......
}
}
在MySql中查看,数据写入成功。
实际应用场景
可以看到,这次的文件中有一万多条,是疫情时期的真实数据。使用mapreduce能很快地对这么多数据进行统计分析。
案例1:日期转换
需求:把文件数据日期转换成xxxx年xx月xx日。
public class Date {
//Mapper类
public static class DateFormatMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
private Text k=new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//获取一行数据
String text=new String(value.getBytes(),0,value.getLength(),"GBK");
//以逗号分割,因为我们的文件是.csv
String[] line = text.split(",", -1);
//计算日期
String date="2021年"+line[0];
k.set(date+","+line[1]+","+line[2]+","+line[3]+","+line[4]+","+
line[5]+","+line[6]+","+line[7]);
context.write(k,NullWritable.get());
}
}
//Reducer和Driver类自行编写
}
日期转换成功。把输出的文件后缀改为csv就可以用excel表格更清楚地查看。
案例2:过滤+分区
需求:对于2021年疫情数据,过滤出省份为湖北省的数据,非湖北省的数据单独放在一个文件夹中,只需要前6列。
public class Data implements WritableComparable<Data> {
//定义6个变量用来存储6列数据
//都设置为String是因为后四个数据都可能为空,不能设置为int
private String date;
private String city;
private String district;
private String sick;
private String discharge;
private String death;
//重载比较方法,使最后数据能够按顺序输出
@Override
public int compareTo(Data o) {
int i = date.compareTo(o.date);
int j = city.compareTo(o.city);
int m = district.compareTo(o.district);
int n = sick.compareTo(o.sick);
int g = discharge.compareTo(o.discharge);
int h = death.compareTo(o.death);
if (i == 0) {
if (j == 0) {
if (m == 0) {
if (n == 0) {
if (g == 0) {
return h;
} else {
return g;
}
} else {
return n;
}
} else {
return m;
}
} else {
return j;
}
} else {
return i;
}
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Data data = (Data) o;
return Objects.equals(date, data.date) &&
Objects.equals(city, data.city) &&
Objects.equals(district, data.district) &&
Objects.equals(sick, data.sick) &&
Objects.equals(discharge, data.discharge) &&
Objects.equals(death, data.death);
}
@Override
public int hashCode() {
return Objects.hash(date, city, district, sick, discharge, death);
}
//序列化和反序列化以及其他方法自行编写
}
public class Hubei {
//过滤出湖北省的分区
public static class DataPartioner extends Partitioner<Data, NullWritable> {
//重写分区方法
@Override
public int getPartition(Data data, NullWritable nullWritable, int numPartitions) {
//比较开头是否为湖北,是湖北就符合条件,筛选出来
if(data.getCity().startsWith("湖北")){
return 0;
}
return 1;
}
}
//Mapper, Reducer和Driver类自行编写
//删除输出路径下的一切文件夹和文件
public static void deleteFile(File file){
File[] files = file.listFiles();
if(files!=null&&files.length!=0){
for (int i = 0; i < files.length; i++) {
deleteFile(files[i]);
}
}
file.delete();
}
public static void main(String[] args) throws InterruptedException, IOException, ClassNotFoundException {
args = new String[]{"D:\\input\\covid19","D:\\output\\covid19.2"};
File file=new File(args[1]);
if(file.exists()){
deleteFile(file);
initDriver(args);
}
else{
initDriver(args);
}
}
}