688
社区成员
发帖
与我相关
我的任务
分享| 这个作业属于哪个课程 | 2023年福大-软件工程;软件工程实践-W班 |
|---|---|
| 这个作业要求在哪里 | 软件工程实践第二次作业——个人实战 |
| 这个作业的目标 |
1、爬取澳网的选手信息和比赛结果数据 2、编写控制台程序根据输入文件输出相应的文件 3、性能分析和优化 4、使用单元测试 5、异常处理分析 6、撰写博客 |
| 其他参考文献 | CSDN、bilibili、bing搜索 |
| PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 30 | 30 |
| • Estimate | • 估计这个任务需要多少时间 | 30 | 30 |
| Development | 开发 | 580 | 1015 |
| • Analysis | • 需求分析 (包括学习新技术) | 90 | 100 |
| • Design Spec | • 生成设计文档 | 30 | 35 |
| • Design Review | • 设计复审 | 30 | 10 |
| • Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 20 | 30 |
| • Design | • 具体设计 | 20 | 30 |
| • Coding | • 具体编码 | 180 | 180 |
| • Code Review | • 代码复审 | 30 | 30 |
| • Test | • 测试(自我测试,修改代码,提交修改) | 180 | 600 |
| Reporting | 报告 | 60 | 70 |
| • Test Report | • 测试报告 | 30 | 30 |
| • Size Measurement | • 计算工作量 | 10 | 10 |
| • Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 20 | 30 |
| 合计 | 670 | 1115 |

1月23-29日数据获取流程:
进入result
打开F12找到result文件
将获取的内容复制到在线json格式转换网站,保存转换后的文件到本地
使用第三方的Gson库将Json文件反序列化,将需要用的数据封装到相应的JPlayers类和JResult类,再从对象中获取数据。
按行读取输入文件,判断指令并保存到ArrayList集合中,供后续按照指令解析json。
调用解析指令的函数,返回对应的内容。用StringBuilder数组保存,并判断之前是否解析过,用BufferWriter写入输出文件。
使用Junit自动生成测试函数,自行设计测试用例进行测试。
删除空行,和多余的大括号,将StringBuffer改为速度更快的StringBuilder。
将测试和优化完成的项目打包成Jar包,构建后将jar文件放在src同级目录下,检查输出文件是否正确
//主类,包括main函数,传入实参input.txt和outp.txt
public class AOSearch
//核心类,包括文件解析和指令解析的函数
public class Lib
//选手类,类成员名称与所需要的Json属性一一对应
public class JPlayers
//结果类,类成员名称与所需要的Json属性一一对应
public class JResult
//功能:初始化用来存放选手信息和比赛结果的String列表
public Lib() ;
//功能:打开input.txt,按行读取文件
//参数:输入文件的路径
//返回:存放每行内容的List<String>
public static List<String> input_txt_analyse(String path);
//功能:解析input.txt的行内容
//参数:行内容
//返回:规定的指令类型
public static String content_analyse(String content);
//功能:解析players.json
//返回:存放players内容的字符串
public static String players_analyse();
//功能:解析result.json
//参数:日期
//返回:存放指定日期的比赛结果的字符串
public static String result_analyse(String date);
//功能:返回team_id对应的player名字并判断人数
//参数:team_id和result类
//返回:人名简写
public static List<String> get_name(String team_id, JResult result);
//功能:将指令结果存入output.txt
//参数:存放对应指令的List集合,output.txt文档名字
public static void output_txt_operate(List<String> input_content,String output_name);
关键函数流程图

关键:
1.跳过空行;2.使用List存储。
public static List<String> input_txt_analyse(String path) throws IOException {
List<String> input_content_list = new ArrayList();
//判断文件是否为空
File file = new File(path);
if (!file.exists()) {
System.out.println("no file");
System.exit(0);
}
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(path),"UTF-8"));
//按行读取
String file_content = bufferedReader.readLine();
//判断空行
while (file_content!=null) {
if(!file_content.equals(""))//如果是空行不进入内容判断
input_content_list.add(content_analyse(file_content)); //把每个指令的判断加入input_content_list中
//空行直接读取下一行
file_content = bufferedReader.readLine();
}
bufferedReader.close();
// System.out.println(input_content_list);
return input_content_list; //返回input.txt里面的输入指令所有内容
}
关键:
1.删去头尾空格;2.删去中间空格;3.将Q1-4转换成对应日期返回;4.区分result指令后的字符串是否为纯数字。 返回指令有四种:
1.“players”:输出所有选手的信息;
2.“四位数字”:表示日期,输出相应日期的比赛结果;
3.“error”:指令有误;
4.“N/A”:指令正确,日期有误。
public static String content_analyse(String content) {
//首先对内容进行规范化,删去头尾空格
content = content.trim();
//判断players
if (content.length() == 7) {
if (content.equals("players")) return "players";
else return "error";
} else if (content.length() == 6) {//只有指令没有日期
if (content.equals("result")) return "N/A";
}else if (content.substring(0, 7).equals("result ")) {
if (content.length() >= 9) {
//删去中间空格
content = content.replaceAll(" +", "");
if (content.length() == 10) {//四位数字
//日期在比赛期间
String temp = content.substring(6, 10);//截取日期
//字符串转整型
if (temp.matches("[0-9]+") == false) {
return "N/A";
} else {
int date = Integer.valueOf(temp);
if (116 <= date && date <= 129) return temp;
else return "N/A";
}
} else if (content.length() == 8) {//Q1-4
String temp = content.substring(6, 8);
if (temp.equals("Q1")) {
return "0109";
} else if (temp.equals("Q2")) {
return "0110";
} else if (temp.equals("Q3")) {
return "0111";
} else if (temp.equals("Q4")) {
return "0112";
} else return "N/A";
} else return "N/A";//日期在比赛时间外
} else return "N/A";//result正确,指令短了
} else return "error";//result指令错误
return "error";
}
关键: 使用StringBuilder存储所有数据
public static String players_analyse() {
//解析players.json
Gson gson = new Gson();
try {
// System.out.println("0");
JsonReader reader = new JsonReader(new FileReader("src/data/players.json"));
//从json获取数据对应到Jplayers类
JPlayers players = gson.fromJson(reader,JPlayers.class);
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < players.players.size(); i++) {//将相应字段写入stringbuffer
//存入full_name
stringBuilder.append("full_name:");
stringBuilder.append(players.players.get(i).full_name);
stringBuilder.append("\n");
//存入性别gender
stringBuilder.append("gender:");
if (players.players.get(i).gender.equals("M"))
stringBuilder.append("male");
else if (players.players.get(i).gender.equals("F"))
stringBuilder.append("female");
stringBuilder.append("\n");
//存入国籍nationality
stringBuilder.append("nationality:");
stringBuilder.append(players.players.get(i).nationality.name);
stringBuilder.append("\n");
stringBuilder.append("-----");
stringBuilder.append("\n");
}
final_content.add(0,stringBuilder.toString());
// System.out.println(stringBuilder);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// System.out.println(final_content.get(0));
return final_content.get(0);
}
关键:
1.获取matches_status的code属性判断是否有选手弃赛;2.使用两个List来存放分数,交替输出;3.输出分数的时候最后一个比分后不加“|”。
public static String result_analyse(String date) {
Gson gson = new Gson();
String file = "src/data/results/" + date + ".json";
String team_id = null;
try {
JsonReader reader = new JsonReader(new FileReader(file));
JResult result = gson.fromJson(reader,JResult.class);
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < result.matches.size(); i++)
if (result.matches.get(i).match_status.code.equals("W")) {
stringBuilder.append("W/O");
stringBuilder.append("\n");
stringBuilder.append("-----");
stringBuilder.append("\n");
} else {
stringBuilder.append("time:");
stringBuilder.append(result.matches.get(i).actual_start_time);
stringBuilder.append("\n");
stringBuilder.append("winner:");
List<String> score_team1 = new ArrayList<>();//存放第一个队的分数
List<String> score_team2 = new ArrayList<>();//存放第二个队的分数
for (int j = 0; j < result.matches.get(i).teams.size(); j++) {
team_id = result.matches.get(i).teams.get(j).team_id;
//判断这个组是否胜利
if (result.matches.get(i).teams.get(j).status != null) {
List<String> players_name = get_name(team_id, result);
//若人数>1为双人赛,否则为单人赛
if (players_name.size() == 1)
stringBuilder.append(players_name.get(0));
else stringBuilder.append(players_name.get(0) + " & " + players_name.get(1));
stringBuilder.append("\n");
//输出比分
stringBuilder.append("score:");
}
//将第二组的score存入score_team2
//输出分数,数组长度等于轮数
//team始终只有两组,j的值为0或1,0表示第一组,1表示第二组
//将第一组的score存入score_team1
if (j == 0) for (int l = 0; l < result.matches.get(i).teams.get(j).score.size(); l++)
score_team1.add(result.matches.get(i).teams.get(j).score.get(l).game);
else for (int l = 0; l < result.matches.get(i).teams.get(j).score.size(); l++)
score_team2.add(result.matches.get(i).teams.get(j).score.get(l).game);
}
for (int r = 0; r < score_team1.size(); r++)
if (r == score_team1.size() - 1)//最后一个比分后没有分隔符“|”
stringBuilder.append(score_team1.get(r) + ":" + score_team2.get(r));
else stringBuilder.append(score_team1.get(r) + ":" + score_team2.get(r) + " | ");
stringBuilder.append("\n");
stringBuilder.append("-----");
stringBuilder.append("\n");
}
return String.valueOf(stringBuilder);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return null;
}
关键:
1.使用is_players和is_date[]来标记是否是第一次解析json,避免重复解析;2.关闭文件流。
public static void output_txt_operate(List<String> input_content,String output_name) throws IOException {
File file = new File(output_name);
if(!file.exists()) file.createNewFile();//如果不存在,就新建文件
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(output_name),"UTF-8"));
boolean is_players = false;//标记是否曾经解析players
boolean[] is_date = new boolean[25];//标记是否解析date
for (int i = 0; i < is_date.length; i++) {//初始化
is_date[i] = false;
}
for (String content:input_content) {
if (content.equals("players")) {
if (!is_players){//player还未解析过//写入数组
players_analyse();
}
is_players = true;//从string写入文件
bw.write(final_content.get(0).toString());
} else if (content.equals("error")) bw.write("Error" + "\n" + "-----" + "\n");
else if (content.equals("N/A")) bw.write("N/A" + "\n" + "-----" + "\n");
else {
int date = Integer.valueOf(content);//日期字符变为整型
date = date % 109 + 1;
if (!is_date[date]) {//第一次读取该日期内容
final_content.add(date,result_analyse(content));
is_date[date] = true;
}
bw.write(String.valueOf(final_content.get(date)));
}
}
bw.close();
}
因为代码中大量使用到StringBuffer,经搜索发现StringBuilder更加适合。原因是,StringBuffer在多线程下更安全但是速度稍慢,StringBuilder适用于单线程且速度快,因为本次作业只使用了单线程,所以将StringBuffer改为StringBuilder。
将代码的细节进行优化,如:字符串变量和字符串常量equals的时候将字符串常量写在前面;增加代码可读性。
优化后的程序运行一百万指令的性能快照如图,虽然时间提升了,但是空间占用很大,暂时还没想出更好的办法降低空间复杂度,希望下次作业能优化这个问题。
对核心的Lib的函数进行测试,这里使用了Junit库来生成test函数。
编写测试函数
测试结果
返回结果符合预期
Lib的覆盖率也达到标准
try {
lib.output_txt_operate(lib.input_txt_analyse(args[0]),args[1]);
} catch (IOException e) {
e.printStackTrace();
}
public static List<String> input_txt_analyse(String path) throws IOException {
List<String> input_content_list = new ArrayList();
//判断文件是否为空
File file = new File(path);
if (!file.exists()) {System.exit(0);}
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(path),"UTF-8"));//按行读取
String file_content = bufferedReader.readLine();//判断空行
while (file_content!=null) {
if(!file_content.equals(""))//如果是空行不进入内容判断
input_content_list.add(content_analyse(file_content)); //把每个指令的判断加入input_content_list中//空行直接读取下一行
file_content = bufferedReader.readLine();
}
bufferedReader.close();
// System.out.println(input_content_list);
return input_content_list; //返回input.txt里面的输入指令所有内容
}
if (args.length != 2) {
System.out.println("Please input two parameter");
System.exit(-1);
}
这次作业给了两周的时间,前一周因为在准备缓考的考试就耽误了,结果第二周疯狂赶进度,ddl的逼近和打代码遇到的困难都给了我很大压力。已经不知道修改了多少bug,学习了多少新的知识了,连复盘的时间都没有,因为不能停下T T。
虽然压力大,但是收获也大,这次的作业让我更加有编码的信心,对需求的分析和实现的流程也更熟悉,接下来希望在下一次作业里能将这次不足的地方做得更好!
时间紧张的情况下还能做的这么详细,上心了!
谢谢助教