1.项目目标
1.1 需求概述
现在我们要统计某学校学生的成绩信息,筛选出成绩在60分及以上的学生。
1.2 业务分析
如果我们想实现该需求,可以通过编写一个MapReduce程序,来处理包含学生信息的文本文件,每行包含【学生的姓名,科目,分数】,以逗号分隔,要求如下:
分别编写一个Student类和一个Mapper类;
Student 类包含以下字段:姓名(String)、科目(String)、分数(int);
需要自定义 Student 对象的序列化和反序列化方法,以便Hadoop能够正确处理它;
Mapper类将输入文本数据解析为 Student 对象,并且只输出成绩大于60分的学生;
以固定字符串"学生信息"为Key,Student对象为Value,作为该Mapper的输出。
2. 新建项目
如果没有进行配置项目,则需要先进行配置,可以参考下文的前半部分:
大数据处理从零开始————8.基于Java构建WordCount项目-CSDN博客
如果配置好就可以直接进行下一步。新建项目后,确定好项目的名称,组ID,工作ID的信息。
打开pom.xml文件,添加项目配置,添加如下配置。
<dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><!--<scope>test</scope>--></dependency><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-common</artifactId><version>3.3.3</version></dependency><!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-client --><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-client</artifactId><version>3.3.3</version></dependency><!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-hdfs --><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-hdfs</artifactId><version>3.3.3</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.11</version></dependency></dependencies>
如果pom.xml全部标红可以看上一节解决方法,完全相同。
3.完善项目代码
3.1 创建Student类
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;public class Student implements Writable {private String name;private String subject;private int score;public Student() {}public Student(String name, String subject, int score) {this.name = name;this.subject = subject;this.score = score;}public void write(DataOutput out) throws IOException {out.writeUTF(name);out.writeUTF(subject);out.writeInt(score);}public void readFields(DataInput in) throws IOException {name = in.readUTF();subject = in.readUTF();score = in.readInt();}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSubject() {return subject;}public void setSubject(String subject) {this.subject = subject;}public int getScore() {return score;}public void setScore(int score) {this.score = score;}@Overridepublic String toString() {return "姓名:" + name + ", 科目:" + subject + ", 分数:" + score;}
}
在这里可能由很多人不明白,我们来解释一下这段代码的关键点:
首先它实现了Writable接口,Writable接口主要实现了序列化和反序列化。Writable接口定义了两个方法。1. void write(DataOutput out):主要用于将对象的字段写入数据输入流DataOutput中;在实现此方法时,需要按照某种顺序依次写入对象的所有属性,确保与readFields方法对应。2.void readfields(DataInput in):该方法用于从数据输入流 DataInput
中读取对象的字段;按照与 write
方法相同的顺序读取,确保数据能正确映射回对象的属性。
其次重写了toString方法。
3.2 实现Mapper类
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;public class StudentMapper extends Mapper<LongWritable, Text, Text, Student> {private static final Text outputKey = new Text("学生信息");@Overrideprotected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {// 解析每行文本String line = value.toString();String[] fields = line.split(",");// 创建Student对象if (fields.length == 3) {String name = fields[0].trim();String subject = fields[1].trim();int score = Integer.parseInt(fields[2].trim());// 只输出成绩大于60分的学生if (score > 60) {Student student = new Student(name, subject, score);context.write(outputKey, student);}}}
}
首先通过泛型确保了输入形式是key——value的形式。
然后创建了一个名为 outputKey
的常量,它代表输出的键,值为 "学生信息"。这个常量在整个类中是共享的,避免了在每次调用 map
方法时重复创建。
接着实现了map方法,在map方法中先将输入值逐行改成字符串数组形式;然后检查字段数组是否为三,确保数组行格式正确,接着分别提取姓名、科目和分数,并进行适当的修剪以去除多余的空格,进一步使用 Integer.parseInt()
方法将分数字符串转换为整数;最后输出大于60分的人。
3.3 构建驱动器StudentDriver类
在构建完map类后,程序并不能直接运行,因此我们还要构建一个Driver类来驱动程序。Driver类是一个 Hadoop MapReduce 程序的驱动程序,负责设置作业的配置并启动 MapReduce 任务。
import org.apache.hadoop.conf.Configuration; // 导入 Hadoop 配置类
import org.apache.hadoop.fs.Path; // 导入路径类,用于文件路径处理
import org.apache.hadoop.io.Text; // 导入 Text 类,表示字符串形式的键
import org.apache.hadoop.mapreduce.Job; // 导入 Job 类,用于定义一个 MapReduce 作业
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; // 导入输入格式类
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; // 导入输出格式类 public class StudentDriver { public static void main(String[] args) throws Exception { // 检查命令行参数数量,要求输入两个参数 if (args.length != 2) { System.err.println("Usage: StudentDriver <input path> <output path>"); System.exit(-1); // 如果参数数量不对,打印用法并退出 } // 创建 Hadoop 配置对象 Configuration conf = new Configuration(); // 创建一个新的 Job 实例,作业名称为 "Student Info Filter" Job job = Job.getInstance(conf, "Student Info Filter"); // 设置程序的 Jar 文件位置 job.setJarByClass(StudentDriver.class); // 设置使用的 Mapper 类 job.setMapperClass(StudentMapper.class); // 设置 Mapper 的输出键值类型 job.setMapOutputKeyClass(Text.class); // 输出键的类型为 Text job.setMapOutputValueClass(Student.class); // 输出值的类型为 Student(自定义类型) // 设置最终输出的键值类型 job.setOutputKeyClass(Text.class); // 输出键的类型为 Text job.setOutputValueClass(Student.class); // 输出值的类型为 Student // 设置输入和输出路径 FileInputFormat.addInputPath(job, new Path(args[0])); // 设置输入路径 FileOutputFormat.setOutputPath(job, new Path(args[1])); // 设置输出路径 // 提交作业,并根据完成情况退出 System.exit(job.waitForCompletion(true) ? 0 : 1); }
}
详细解释看代码注释。
4.运行程序
首先给程序打包。
然后给程序重命名。
接着上传到hadoop中。
启动hadoop,并将程序上传到hadoop中。
./myhadoop.sh start 启动hadoop
cd /user/local/data #转到文件夹下
ll #查看是否上传成功
vim student #创立测试文件
hdfs dfs -put student /cs #将student上传到/cs中
注意:我这里有cs,如果没有的话,应该先用下面命令创建/cs
hdfs dfs mkdir /cs #在hadoop下创建一个cs文件。
运行jar包:hadoop jar map.jar com.jyd.StudentDriver /cs /ssoutput
查看结果: hdfs dfs -cat /ssoutput/part-r-00000