这个作业属于哪个课程 | <软件工程23年春季> |
---|---|
这个作业要求在哪里 | <软件工程实践第二次作业—文件读取> |
这个作业的目标 |
|
其他参考文献 | 《构建之法》、csdn、博客园 |
1.Gitcode项目地址
2.PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 10 |
• Estimate | • 估计这个任务需要多少时间 | 20 | 10 |
Development | 开发 | 600 | 1220 |
• Analysis | • 需求分析 (包括学习新技术) | 120 | 150 |
• Design Spec | • 生成设计文档 | 60 | 30 |
• Design Review | • 设计复审 | 30 | 30 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
• Design | • 具体设计 | 60 | 90 |
• Coding | • 具体编码 | 300 | 590 |
• Code Review | • 代码复审 | 40 | 80 |
• Test | • 测试(自我测试,修改代码,提交修改) | 90 | 210 |
Reporting | 报告 | 90 | 120 |
• Test Repor | • 测试报告 | 30 | 80 |
• Size Measurement | • 计算工作量 | 15 | 10 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 45 | 30 |
合计 | 1560 | 1350 |
3.解题思路描述
3.1总体思路
3.2 json数据获取
使用FireFox的开发者工具获取澳大利亚网球公开赛官网的数据(该爬取行为仅用于课程教学),找到对应的json文件双击后下载到本地。
• players的数据获取
• results的数据获取
3.3 解析数据
使用第三方的Gson对本地的json文件进行数据的解析,并使用Map暂存对象数据,List <Map>暂存数组数据。(此前还以为Gson解析都要用bean封装)
参考:Gson实现json与map互相转换
- 对players解析:
需要的数据为选手的full_name
,gender
,nationality
,直接获取players数组后,遍历该数组,并且使用get()直接获取对应数据,获取nationality对象后还再获取其name字段。
- 对results解析:
需要的数据为比赛的time
,winner
,score
,处理流程如下:
- 检查比赛状态,若弃赛直接获取addr数据W/O;正常比赛获取如图的
time
数据,获胜队伍的team_id
用于在teams数组中查找获胜队伍;
2.找到获胜选手的uuid后在players中查找选手,获取winner
数据:
3.对应图一的teams中我们可以获得队伍比分情况,获取到score
数据:
3.4 文件读写
1.读取input中的指令:readline()一行行遍历input.txt的指令,并将遍历的指存储到一个List中。
2.根据指令List获取对应数据:首先获取指令List,然后遍历指令(此时进行基本的指令正误判断),根据指令获取对应的players与result date数据。
3.将数据写入output文件:获得对应数据后遍历数据写入文件中。
4.接口设计和实现过程
4.1总体接口设计:
主要是两个类,一个AOSearch类(作为入口),一个Utils类(提供接口函数)。
- 其中
analyseResult()
调用getWinner()
、getMatchInfo()
、getWinnerUuid()
等方法来获取比赛结果字符串。 checkCmd()
方法根据指令,进行判断,返回相应数据;被getData()
调用。
4.2 算法关键:
- 利用Gson解析json文件,转化为字符串,其中用Map存储json对象,List<Map>存储json数组。
- 将输出数据的类型进行判断,对应具体指令输出具体数据,使用正则表达式进行判断。
- 使用HashMap来缓存,减少频繁的文件读取次数。
4.3主要的接口函数:
//获取json字符串
public static String getJsonStr(String fileName) throws IOException{...}
//Gson解析运动员信息
public static String analysePlayer() throws IOException{...}
//Gson解析比赛结果
public static String analyseResult(String date) throws IOException{...}
//Gson解析比赛结果:获取胜队的选手uuid
public static List<Map> getWinnerUuid(Map m, List<Map> teams){...}
//Gson解析比赛结果:获取获胜者
public static String getWinner(List<Map> winnerUuid, List<Map> players){...}
//Gson解析比赛结果:获取双方比赛情况
public static String getMatchInfo(Map m){...}
//获取input文件的指令
public static List<String> getInput(String fileName) throws IOException{...}
//根据input指令类型获取写入output的数据
public static List<String> getData(List<String> inputList) throws IOException{...}
//判断指令对应的数据内容类型
public static int checkCmd(String str){...}
//把数据写入输出文件
public static void setOutput(String fileName, List<String> dataList) throws IOException{...}
5.关键代码展示
- getJsonStr函数:获取json数据
• 参数:json文件的相对路径
• 返回:json字符串
• 调用关系:被analyseResult()
与analysePlayer()
函数调用;
public static String getJsonStr(String fileName) throws IOException{
File file = new File(fileName);
if(!file.exists()){
throw new FileNotFoundException("目标json文件未找到,请确保路径正确后重试!");
}
String lineStr;
StringBuilder sb = new StringBuilder();
FileInputStream fins = new FileInputStream(fileName);
BufferedReader reader = new BufferedReader(new InputStreamReader(fins,"UTF-8"));
while((lineStr = reader.readLine()) != null){
sb.append(lineStr);
}
reader.close();
return sb.toString();
}
- analysePlayer函数:Gson解析运动员信息
• 参数:无
• 返回:解析运动员信息后拼接的字符串
• 调用关系:调用getJsonStr()
获取json数据;
public static String analysePlayer() throws IOException{
if(hashMap.containsKey("players")){ //使用hashMap缓存数据
return hashMap.get("players");
}
Gson gson = new Gson();
Map map = gson.fromJson(getJsonStr(playersPath), Map.class);
List<Map> players = (List<Map>) map.get("players");
StringBuilder sb = new StringBuilder();
for(Map p : players){
sb.append("full_name:" + p.get("full_name") + "\n");
String gender = p.get("gender").equals("M") ? "male" : "female";
sb.append("gender:" + gender + "\n");
sb.append("nationality:" + ((Map)p.get("nationality")).get("name") + "\n");
sb.append("-----\n");
}
hashMap.put("players", sb.toString());
return sb.toString();
}
- analyseResult函数:Gson解析比赛结果
• 参数:result xx指令的xx(赛程)
• 返回:解析比赛结果信息后拼接的字符串
• 调用关系:1.调用getJsonStr()
获取json数据;2.调用getWinner()
获取胜利选手的缩写名;3.调用getWinnerUuid()
获取比赛得分信息。
public static String analyseResult(String date) throws IOException{
if(hashMap.containsKey(date)){ //使用hashMap缓存数据
return hashMap.get(date);
}
Gson gson = new Gson();
Map map = gson.fromJson(getJsonStr(resultsPath + date + ".json"), Map.class);
List<Map> matches = (List<Map>)map.get("matches");
List<Map> teams = (List<Map>)map.get("teams");
List<Map> players = (List<Map>)map.get("players");
StringBuilder sb = new StringBuilder();
for(Map m : matches){
Map matchStatus = (Map) m.get("match_status");
if(matchStatus.get("abbr").equals("W/O")){ //如果参赛状态为弃赛
sb.append("W/O" + "\n");
}else{
sb.append("time:" + m.get("actual_start_time") + "\n");
sb.append("winner:" + getWinner(getWinnerUuid(m,teams),players) + "\n");
sb.append("score:" + getMatchInfo(m) + "\n");
}
sb.append("-----\n");
}
hashMap.put(date, sb.toString());
return sb.toString();
}
- getWinner函数:获取获胜者
• 参数:1.获胜者选手的uuid;2.players的对象数组
• 返回:获胜者缩写名的字符串
• 调用关系:被analyseResult()
函数调用;
public static String getWinner(List<Map> winnerUuid, List<Map> players){
StringBuilder sb = new StringBuilder();
List<Map> winners = new ArrayList<>();
for(int i = 0; i < winnerUuid.size(); i++){
for(Map p : players){
if(p.get("uuid").equals(winnerUuid.get(i))){ //1.获取获胜者List
winners.add(p);
}
}
}
for(int j = 0; j < winners.size(); j++){ //2.遍历获胜者名字
sb.append(winners.get(j).get("short_name"));
if(winners.size() - j > 1){
sb.append(" & ");
}
}
return sb.toString();
}
6.性能改进
6.1 性能分析工具
使用的性能分析工具是Jprofiler11
参考:IDEA集成JProfiler11可视化工具
6.2.1 思路一:使用StringBuilder处理字符串
使用StringBuilder来拼接字符串并不会产生新的对象,减少了频繁创建对象的耗时。
参考:StringBuffer和StringBuilder区别详解
6.2.2 思路二:使用HashMap缓存
使用HashMap来缓存players和result date数据,这样当重复读取时,不需要频繁的读取文件操作,节省较多的时间。
6.3 优化结果
分别测试未优化前,思路一优化,和思路二优化的代码跑一万条指令的时间,结果如下:
• 未优化:
• 思路一优化:
优化效果不佳,后来发现是测试数据不够大,拼接次数不足,导致差异不明显。
• 思路二优化:
运行时间大大缩短!
由于此次作业为文件读取,所以主要的优化思路应该放在减少文件读取次数上。
7.单元测试
7.1 测试工具
使用的性能测试工具为Junit5。
参考:1.IDEA中使用JUnit4 2.junit5详解
7.2覆盖率测试与测试样例
一.对AOSearch测试:
@Test //对命令行参数测试
public void main() {
AOSearch.main(new String[]{"input.txt","output.txt"});
AOSearch.main(new String[]{"input1.txt","output.txt"});
AOSearch.main(new String[]{"output.txt"});
AOSearch.main(new String[]{"input.txt"});
AOSearch.main(new String[]{"input.txt","output.txt","output.txt"});
}
二.对Utils部分测试:
1.对获取数据的方法单元测试:
@Test
public void analyseTest(){
assertDoesNotThrow(()->Utils.analyseResult("Q1"));
assertDoesNotThrow(()->Utils.analysePlayer());
assertThrows(IOException.class, ()->Utils.analyseResult(" "));
assertThrows(IOException.class, ()->Utils.analyseResult("01116"));
assertThrows(IOException.class, ()->Utils.analyseResult(" 0116"));
assertThrows(IOException.class, ()->Utils.analyseResult("0115"));
assertThrows(IOException.class, ()->Utils.analyseResult("0130"));
assertThrows(IOException.class, ()->Utils.analyseResult("Qq"));
}
2.对checkCmd()方法单元测试:
@Test //对指令进行测试
public void checkCmdTest(){
//正确的指令
assertEquals(1,Utils.checkCmd("players"));
assertEquals(2,Utils.checkCmd("result Q1"));
assertEquals(2,Utils.checkCmd("result 0116"));
//错误的指令
assertEquals(-1,Utils.checkCmd("Players"));
assertEquals(-1,Utils.checkCmd("player"));
assertEquals(-1,Utils.checkCmd("Result 0116"));
assertEquals(-1,Utils.checkCmd("resu lt Q1"));
assertEquals(0,Utils.checkCmd("result"));
assertEquals(0,Utils.checkCmd("result "));
assertEquals(0,Utils.checkCmd("result 0116 002"));
assertEquals(0,Utils.checkCmd("result 0116002"));
assertEquals(0,Utils.checkCmd("result 016"));
assertEquals(0,Utils.checkCmd("result 0130"));
assertEquals(0,Utils.checkCmd("result q1"));
assertEquals(0,Utils.checkCmd("result Q 1"));
}
8.异常处理
8.1 命令行参数异常
这里处理了命令行参数的异常情况,并对Utils里方法的异常进行捕获。
public static void main(String[] args) {
Utils U = new Utils();
try {
if(args.length != 2){ //命令行参数错误
throw new IOException("输入参数格式有误,应为:Java -jar AOSearch.jar input.txt output.txt");
}
U.setOutput(args[1], U.getData(U.getInput(args[0])));
} catch (Exception e) {
e.printStackTrace();
}
}
8.2 JSON文件未找到异常
File file = new File(fileName);
if(!file.exists()){
throw new FileNotFoundException("目标json文件未找到,请确保路径正确后重试!");
}
9.心得体会
- 一开始以为这次作业只是解析json然后文件读写,但是实际上手后,发现还有许多东西以前没有接触过,比如:单元测试、性能分析等。来来回回参考了很多博客,折腾了也挺久的,好在基本的问题都能够得到解答。
- 对于时间安排问题,自己以后开始之前还是要正确评估一下到底需要多少时间,以免快到ddl的时候还没写完。
- 最后,感觉自己这次才对性能优化有了一些了解,对开发者来说优化很重要,对客户更是如此。看来还是需要不断学习拓宽自己的知识面!(ง •_•)ง加油!!