686
社区成员




这个作业属于哪个课程 | 2023年福大-软件工程实践-W班 |
---|---|
这个作业要求在哪里 | 软件工程实践第二次作业——个人实战 |
这个作业的目标 | GitCode项目上传、代码编写并测试、博客撰写 |
其他参考文献 | 相关链接置于文末 |
仓库地址:project-java
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 25 | 30 |
• Estimate | • 估计这个任务需要多少时间 | 25 | 30 |
Development | 开发 | 940 | 1290 |
• Analysis | • 需求分析 (包括学习新技术) | 60 | 100 |
• Design Spec | • 生成设计文档 | 25 | 30 |
• Design Review | • 设计复审 | 20 | 10 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 25 | 20 |
• Design | • 具体设计 | 45 | 70 |
• Coding | • 具体编码 | 360 | 600 |
• Code Review | • 代码复审 | 45 | 60 |
• Test | • 测试(自我测试,修改代码,提交修改) | 360 | 400 |
Reporting | 报告 | 115 | 105 |
• Test Repor | • 测试报告 | 45 | 60 |
• Size Measurement | • 计算工作量 | 30 | 15 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 40 | 30 |
合计 | 1080 | 1425 |
经过多次阅读题目要求,分析之后得出本次作业可以分为四个部分。首先是从官方网站获取作业所需的数据;二是解析数据,将数据转换为所需要的格式;三是从input文件中读取并分析指令;四是将指令对应的数据写入output文件。
①打开澳大利亚网球公开赛官网,进入开发者模式;
②刷新页面请求数据,找到result双击打开,url即为数据地址;
③将页面数据保存至本地,修改地址中的数字即可跳转至相应日期的赛程数据。
因为上学期的移动应用开发课程有介绍一些json的相关知识,所以选择用gson解析数据。一开始观察数据格式,发现数据量实在是太大了,看的很头疼,不知道应该如何设计实体类,所以选择通过在线转换工具,转换出Javabean,再选择需要的属性,嵌套生成对应的players和schedule类,在项目中导入gson的jar包即可使用。
部分转换效果如图:
使用BufferedReader对input文件进行读取,每读取一行就对指令进行相应的判断,流程图如下:
对于players可以使用解析数据得到的实体类的get方法得到相应的full_name、gender、nationality,使用BufferedWriter的write方法向output文件写入对应格式的数据。
对于赛程数据的输出,首先需要判断该场比赛是否正常进行,还是存在弃赛情况,若比赛正常进行,由于JSON数据的结构并不是直接对应我们需要的数据,需要通过获胜队伍的id在teams列表中找到对应的队伍,获取其包含的队员的id,再从players列表中查找对应的player,得到short_name;弃赛则输出W/O。
首先是JSON数据解析所需要的两个实体类,其内部结构必须与数据结构相同,正确嵌套,对应的名称也要一样。
//省略对应的get、set函数
public class Json2players {
private List<Players> players;
public static class Players {
private String uuid;
private String full_name;
private String gender;
private Nationality nationality;
public static class Nationality {
private String name;
}
}
}
//省略对应的get、set函数
public class Json2schedule {
private List<Matches> matches;
private List<Players> players;
private List<Teams> teams;
public static class Matches {
private String actual_start_time;
private List<Teams> teams;
private Match_status match_status;
public static class Teams {
private String team_id;
private List<Score> score;
private String status;
public static class Score {
private String game;
}
}
public static class Match_status {
private String abbr;
}
}
public static class Players {
private String uuid;
private String short_name;
}
public static class Teams {
private String uuid;
private List<String> players;
}
}
AOSearch类作为程序的入口,进行相应的文件异常的处理,以及程序执行流程的条件判断,调用Lib类中相应的功能函数。
Lib类中包括以下功能函数
//team_id对应player缩写list
public static HashMap<String, List<String>> hashMap=new HashMap<>();
//对player.json进行解析,返回Json2players类对象
public static Json2players analyzeplayers() {...}
//date为指令中的对应日期字符串
//对date.json进行解析,返回Json2schedule类对象
public static Json2schedule analyzeschedule(String date) {...}
//比赛情况输出流程
public static void output(StringBuilder stringBuilder, Json2schedule jsonschedule) throws IOException {...}
//匹配对应获胜队伍的队员缩写
//输出winner
public static void output_winner(StringBuilder stringBuilder, Json2schedule.Matches matches, Json2schedule jsonschedule) {...}
//输出score
public static void output_score(StringBuilder stringBuilder, Json2schedule.Matches matches) {...}
//按格式输出player
public static void output_players(StringBuilder stringBuilder, Json2players jsonplayers) throws IOException {...}
//判断指令
public static String analyzeinstruct(String instruct) {...}
//对player.json进行解析,返回Json2players类对象
public static Json2players analyzeplayers() {
String path="./src/data/players.json";
BufferedReader br=null;
try {
br=new BufferedReader(new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Gson gson=new Gson();
Type type=new TypeToken<Json2players>() {}.getType();
Json2players json_players=gson.fromJson(br,type);
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
return json_players;
}
String instruct;
while ((instruct=reader.readLine())!=null) {
instruct.trim();
StringBuilder sb=new StringBuilder();
//判断指令类型
String str=Lib.analyzeinstruct(instruct);
if (!"null".equals(str)) {//跳过空行
if (!str.equals("N/A")&&!str.equals("Error")) {
if (hashMap.containsKey(str)) {
stringBuilder.append(hashMap.get(str));
}
else {
if (str.equals("players")) Lib.output_players(sb,Lib.analyzeplayers());
else Lib.output(sb,Lib.analyzeschedule(str));
hashMap.put(str,sb);
stringBuilder.append(sb);
}
}
else stringBuilder.append(str).append("\n-----\n");
}
count++;
if (count==10000) {
writer.write(String.valueOf(stringBuilder));
stringBuilder.setLength(0);
count=0;
}
}
writer.write(String.valueOf(stringBuilder));
//判断指令
public static String analyzeinstruct(String instruct) {
String str = "";
if (instruct.equals("")) str="null";//空行
else {
String[] list=instruct.split("\\s+");
if (list.length==0) str="null";
else if ("result".equals(list[0])) {
if (list.length==2&&(list[1].equals("0116")||list[1].equals("0117")||list[1].equals("0118")||list[1].equals("0119")||list[1].equals("0120")
||list[1].equals("0121")||list[1].equals("0122")||list[1].equals("0123")||list[1].equals("0124")||list[1].equals("0125")||list[1].equals("0126")
||list[1].equals("0127")||list[1].equals("0128")||list[1].equals("0129")||list[1].equals("Q1")||list[1].equals("Q2")||list[1].equals("Q3")||list[1].equals("Q4"))) str=list[1];
else str="N/A";
}
else if ("players".equals(list[0])) {
if (list.length==1) str="players";
else str="Error";
}
else if (list[0].equals("")) {
if ("result".equals(list[1])) {
if (list.length==3&&(list[2].equals("0116")||list[2].equals("0117")||list[2].equals("0118")||list[2].equals("0119")||list[2].equals("0120")
||list[2].equals("0121")||list[2].equals("0122")||list[2].equals("0123")||list[2].equals("0124")||list[2].equals("0125")||list[2].equals("0126")
||list[2].equals("0127")||list[2].equals("0128")||list[2].equals("0129")||list[2].equals("Q1")||list[2].equals("Q2")||list[2].equals("Q3")||list[2].equals("Q4"))) str=list[2];
else str="N/A";
}
else if ("players".equals(list[1])) {
if (list.length==2) str="players";
else str="Error";
}
}
else str="Error";
}
return str;
}
//获取获胜队伍id
if ("Winner".equals(matches.getTeams().get(0).getStatus()))
id=matches.getTeams().get(0).getTeam_id();
else id=matches.getTeams().get(1).getTeam_id();
//根据队伍id匹配队伍
//访问过的队伍数据直接从hashmap中获取
//未访问过的通过json数据得到,并放入hashmap
List<String> list=new ArrayList<>();
if (hashMap.containsKey(id)) {
list = hashMap.get(id);
for (int i = 0; i < list.size(); i++) {
str.append(list.get(i));
if (i != list.size() - 1) str.append(" & ");
}
}
else {
for (int j=0;j<jsonschedule.getTeams().size();j++) {
if (id.equals(jsonschedule.getTeams().get(j).getUuid())) {
for (int k=0;k<jsonschedule.getTeams().get(j).getPlayers().size();k++) {
String playerid=jsonschedule.getTeams().get(j).getPlayers().get(k);
//根据playerid匹配short_name
for (int l=0;l<jsonschedule.getPlayers().size();l++) {
if (playerid.equals(jsonschedule.getPlayers().get(l).getUuid())) {
str.append(jsonschedule.getPlayers().get(l).getShort_name());
list.add(jsonschedule.getPlayers().get(l).getShort_name());
break;
}
}
if (k!=jsonschedule.getTeams().get(j).getPlayers().size()-1) str.append(" & ");
}
break;
}
}
hashMap.put(id,list);
}
一开始没有使用StringBuilder记录需要往output文件内写入的内容,最后一次性写入,而是一边判断,执行相应的获取数据功能,一边向文件写入,当写入的数据量很大时,有多少行数据,就要写入多少次,十分耗时;并且还存在每读取一条有效指令,就要打开、关闭一次json数据文件,并且使用Gson进行解析,也是十分耗时的操作。优化前执行32万行有效指令耗时如图:
使用StringBuilder记录需要写入的内容,每次只需要使用append()方法加入,最后只需要向文件写入一次。使用HashMap记录日期与对应的结果,每次读取到有效指令时先判断是否在HashMap中,存在可直接取出对应键值加入StringBuilder中,不存在则执行相应的获取数据的功能函数,并加入HashMAp。第一次优化执行3万行有效指令耗时如图:
当执行30万行有效指令时,程序报错:Java heap space,内存溢出,我认为是因为StringBuilder过大。
再次使用HashMap,用于从team_id匹配到player的short_name,每次查找时,先判断是否在HashMap中,存在则取出缩写的list,不存在则进行解析查找,最后加入HashMap。因为StringBuilder过大,所以增加一个计数的count变量,每读取解析10000条指令,就向output文件输入一次,使用将StringBuilder的length置零的方法清除数据。第二次优化执行3万行有效指令耗时如图:
第二次优化执行32万行有效指令耗时如图,文件大小2.04G:
虽然第二次优化执行3万行有效指令的速度和第一次优化差不多,但是解决了内存溢出问题,并且从优化前到第二次优化后,速度快了95倍!
在CMD中测试32万条有效指令,运行时间会比直接运行源程序慢3秒左右。
@Test
public void analyzeinstruct() {
assertEquals("Error",Lib.analyzeinstruct("12345"));
assertEquals("Error",Lib.analyzeinstruct("resu"));
assertEquals("Error",Lib.analyzeinstruct("resultplayer"));
assertEquals("Error",Lib.analyzeinstruct("pla yers"));
assertEquals("Error",Lib.analyzeinstruct("re sult0120"));
assertEquals("Error",Lib.analyzeinstruct("result0116"));
assertEquals("Error",Lib.analyzeinstruct("player"));
assertEquals("Error",Lib.analyzeinstruct("players 2"));
assertEquals("Error",Lib.analyzeinstruct(" players 11"));
assertEquals("N/A",Lib.analyzeinstruct(" result 0116 23"));
assertEquals("N/A",Lib.analyzeinstruct("result Q 01"));
assertEquals("N/A",Lib.analyzeinstruct("result 01166"));
assertEquals("N/A",Lib.analyzeinstruct("result 0155"));
assertEquals("N/A",Lib.analyzeinstruct("result q"));
assertEquals("N/A",Lib.analyzeinstruct("result"));
assertEquals("null",Lib.analyzeinstruct(""));
assertEquals("null",Lib.analyzeinstruct(" "));
assertEquals("Q4",Lib.analyzeinstruct("result Q4"));
assertEquals("0116",Lib.analyzeinstruct("result 0116"));
assertEquals("0116",Lib.analyzeinstruct("result 0116"));
assertEquals("0129",Lib.analyzeinstruct(" result 0129"));
assertEquals("players",Lib.analyzeinstruct("players"));
assertEquals("players",Lib.analyzeinstruct("players "));
assertEquals("players",Lib.analyzeinstruct(" players "));
}
@Test
public void analyzeschedule() {
assertNotNull(Lib.analyzeschedule("0116"));
assertNotNull(Lib.analyzeschedule("0117"));
assertNotNull(Lib.analyzeschedule("0118"));
assertNotNull(Lib.analyzeschedule("0119"));
assertNotNull(Lib.analyzeschedule("0120"));
assertNotNull(Lib.analyzeschedule("0121"));
assertNotNull(Lib.analyzeschedule("0122"));
assertNotNull(Lib.analyzeschedule("0123"));
assertNotNull(Lib.analyzeschedule("Q1"));
assertNotNull(Lib.analyzeschedule("Q2"));
assertNotNull(Lib.analyzeschedule("Q3"));
}
只对Lib类中的功能函数进行测试
if (args.length!=2) {
System.out.println("输入的文件个数有误!");
System.exit(0);
}
if (!input.exists()) {
System.out.println("未找到该输入文件!请检查您的文件路径:" + args[0]);
System.exit(0);
}
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(args[0]), StandardCharsets.UTF_8));
BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(args[1]), StandardCharsets.UTF_8));
stringBuilder= new StringBuilder();
hashMap= new HashMap<>();
try {
String instruct;
while ((instruct=reader.readLine())!=null) {...}
writer.write(String.valueOf(stringBuilder));
reader.close();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
首先是合理安排时间真的非常重要!因为疫情原因这学期开学还需要进行上学期一些课程的缓考,而且还有本学期课程要上,导致时间非常紧,考试和作业都很重要,只能疯狂挤时间😭😭😭。
然后是要努力提高专注力和效率,这样才能深入地去思考和设计如何实现这些功能,避免磕磕绊绊地编程,保证程序的质量同时节约时间。
再是心态很重要!面对这么多事情的压力,我很担心自己没有办法处理好它们,一开始看到这次实战的项目要求,我的内心是抗拒的,因为涉及到的知识点、方法还有整个设计过程都是我不曾接触、学习的,特别是当我看到庞大的json数据并且担心gson没有办法成功解析的时候,我整个人都非常的消极。所以我觉得保持良好的心态是十分重要的,遇到困难害怕是正常的,但是要有迎难而上的心,而不是知难而退。
最后是多学一点很重要,学无止境,会的越多,在遇到困难的时候能解决的办法就越多,就比如说在解析数据的时候,如果我会使用其他方法解析json数据,那么也就不会担心gson是否能成功,在面对项目要求的时候,如果我学习过性能改进、单元测试这些知识的话,心态是否就大不相同了呢?
在本次作业中,对JSON数据的处理,文件的使用,性能分析以及单元测试等,对我来说都是比较困难的点,特别是性能分析和单元测试,希望在之后的课程能进一步地学习如何设计测试数据,使测试更加有效。
【Java】Map集合概述
Java使用GSON对JSON进行解析
保姆级教程——IDEA中使用Junit进行测试
[JAVA] java: 错误: 无效的源发行版:17
完成度很高,赞!
虽然第二次优化执行3万行有效指令的速度和第一次优化差不多,但是解决了内存溢出问题,并且从优化前到第二次优化后,速度快了95倍!
整个优化过程有思路有数据支持,很好地体现优化过程,赞!
勇敢走出舒适圈,不断挑战自己的上限,就会发现事情并没有那么困难。加油!!!