686
社区成员




这个作业属于哪个课程 | 2023春季软件工程实践-W班(福州大学) |
---|---|
这个作业要求在哪里 | 软件工程实践第二次作业——个人实战 |
这个作业的目标 | 完成对澳大利亚网球公开赛相关数据的收集、掌握json数据的获取和解析、掌握java文件的输入输出、掌握java项目打jar包的方法 |
其他参考文献 | JAVA之单元测试:Junit框架 、 软件测试开发实战|Java版本,Gson解析json比较常用的2种方式 |
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
|
| 30 | 30 |
Development | 开发 | 1270 | 1115 |
|
| 180 | 160 |
|
| 30 | 20 |
|
| 20 | 15 |
|
| 20 | 30 |
|
| 120 | 120 |
|
| 720 | 640 |
|
| 60 | 50 |
|
| 120 | 80 |
Reporting | 报告 | 110 | 110 |
|
| 60 | 50 |
|
| 30 | 30 |
|
| 20 | 30 |
合计 | 1410 | 1255 |
在main函数参数String[] args中,将intput.txt文件的路径赋值给args[0],output.txt文件的路径赋值给args[1]。通过Files.readAllLines()方法来获取input.txt文件中的每一行,再将每一行的内容传入自定义的一个内容分析方法进行判断;通过PrintStream对象的构造函数可以将System.out.println()的打印内容输出到output.txt。
在我最开始读完作业要求后,因为粗心没有看见助教已经提供了部分json数据,我错误地认为所有的json数据都要通过http从澳网的网站上获取。所以我最开始先寻找关于java的okhttp是如何使用的一些参考资料。
(1)通过url请求获取数据:
在澳网的官网中点击schedule模块(后续因为助教更新的json数据是从result模块的接口拿到的数据,所以我后来接口也做了更改,但获得URL请求的方法相同),键盘按下F12打开开发者工具,在开发者工具中点击网络(Network),再点击Fetch/XHR,刷新,可以看到开发者工具里列出了页面的请求列表,找到schedule,点击右侧Headers,在General区域中就可以看到请求的url(Request URL)。(图示以Day14为例)
对url的处理:
我新建了一个类叫UrlList,分别用来存放players、Q、Day这三类请求的url,但对于Day和Q的URL请求需要做一些处理,因为Day和Q有多个不同日期。
用*代替天数所在的位置,后续只需要用字符串替换的方法将对应的天数把"*"号替换掉就行。
最后通过URL对象和HttpURLConnection对象就可以读取到相应json数据。
(2)通过复制Response获得json数据
在完成所有功能之后我发现用URL请求json数据的方法在运行时很受网络状况的影响,于是我决定改成使用本地json文件来实现。
数据的获取与之前差别不大,只需要将点击Header那步改为点击Response,就可以看到该接口的json数据,将其复制下来,格式化后贴入自己在data文件夹下新建的空白json文件中即可。
在解析json数据前首先要熟悉需要解析的文件结构,判断想获取的数据是存储在对象中还是数组中,甚至是嵌套着数组的对象中,需要去一层层地解析。
在解析json前需要先创建一些实体类来存放解析出来的数据。
如果目前解析的元素是对象,就用JsonObject的getAsJsonObject方法来拿到对象,用getAsString将对象某个值作为字符串存入实体类的对应变量中;如果解析的是数组,就用JsonArray的getAsJsonArray()方法来得到数组,再通过遍历数组将每一个数组元素存入实体类的list中。
eg:以players的full_name解析为例,可以看到players是一个对象数组,每个数组元素中的full_name是一个字符串
则full_name的解析为:
JsonArray playerArray = object.get("players").getAsJsonArray(); List playerList = new ArrayList<>(); for (JsonElement player : playerArray) { String fullName = player.getAsJsonObject().get("full_name").getAsString(); }
在AOSearch类中,我通过参数args和PrintStream对象实现了input.txt文件内容的输入和将打印结果输出到文件output.txt。创建了一个私有变量playerList来存放获取到的所有选手信息,获取方法是Lib接口中的getPlayersMap()。将input.txt的内容和playerList作为参数传入Lib接口的contentIdentify()方法中对输入内容进行分析。
在contentIdentify()方法中创建一个list变量来存放每一行用空格分隔开的内容。如果该行第一个参数和字符串“players”一样,且该行通过空格分隔后的list只有一个元素,则该行的命令为让我们输出选手信息,调用outputPlayer()方法;如果该行第一个参数为result,且该行通过空格分隔后的list有两个元素,则调用resultIdentify()方法进入下一步对日期和赛季的判断。
在resultIdentify()中,我将list元素第二个参数与“0116”······“”0129进行比对,如果与某个日期相同,调用outputDay()方法输出当日比赛结果;与”Q1“······”Q4“进行比对,如果与某个赛季相同,则调用outputQuarter()方法输出该赛季比赛结果。
players解析:players数组->字符串uuid、short_Name、full_name、gender,对象nationality->字符串name,
创建PlayerDTO对象list,将对应信息存入;
创建MatchDTO对象list,
actual_start_time解析:matches数组->每个数组的actual_start_time,存入MatchDTO对象;
创建TeamDTO对象list,解析得到score、game、set和winner的值并存入TeamDTO对象,解析得到winner内层status的值以及team_id用于后续输出时弃赛状态的判断和比赛获胜队伍队员名字的输出。
大部分输出信息可以解析后不做改动存入实体类中直接输出,而winner的输出需要对bool值进行判断以及获胜者的名字缩写需要通过team_id找到队伍中players的uuid,然后后根据uuid找到该player对应的名字缩写,这个判断过程较为繁琐,需要频繁解析json数据,刚开始我是通过流式编程的方式,可以提高程序的简洁性。但在之后的优化中,我放弃了这种方式用map数据结构进行了优化。
PlayerDTO playerDTO = playerList.stream().filter(p -> p.getUuid().equals(playerId)).findFirst().orElse(null);
AOSearch.java:
public static void main(String[] args) throws IOException{
String inputPath = "input.txt";
String outputPath = "output.txt";
try
{
//使用printStream将sout的内容输出到文件
PrintStream out = new PrintStream(outputPath);
System.setOut(out);
}
catch(FileNotFoundException e)
{
e.printStackTrace();
}
Path path = Paths.get(inputPath);
playerList=Lib.getPlayersMap();
List<String> lines = Files.readAllLines(path);
for (String line : lines) {
Lib.contentIdentify(line,playerList);
}
}
contentIdentify()方法:
public static void contentIdentify(String line,Map<String,PlayerDTO> playerMap,List<PlayerDTO> playersList) throws IOException {
List<String> lineList = Arrays.asList(line.split(" "));
if (lineList.get(0).equals(PLAYER) && lineList.size() == 1) {
outputPlayer(playersList);
} else if (lineList.get(0).equals(RESULT) && lineList.size() == 2) {
resultIdentify(lineList.get(1), playerMap);
} else {
System.out.println("Error");
System.out.println("-----");
}
}
getPlayersList()方法:
public static List<PlayerDTO> getPlayersList() throws IOException{
String filePath="src\\data\\players.json";
JsonObject object= (JsonObject) new JsonParser().parse(new FileReader(filePath));
JsonArray playerArray = object.get("players").getAsJsonArray();
List<PlayerDTO> playersList=new ArrayList<>();
for (JsonElement player : playerArray) {
String uuid = player.getAsJsonObject().get("uuid").getAsString();
String shortName = player.getAsJsonObject().get("short_name").getAsString();
String fullName = player.getAsJsonObject().get("full_name").getAsString();
String gender = player.getAsJsonObject().get("gender").getAsString();
String nationality = player.getAsJsonObject().get("nationality").getAsJsonObject().get("name").getAsString();
playersList.add(new PlayerDTO(uuid, shortName, fullName, gender, nationality));
}
return playersList;
}
getMatchList()方法:
public static List<MatchDTO> getMatchList(JsonObject response, Map<String,PlayerDTO> playerMap) {
JsonArray matches = response.get("matches").getAsJsonArray();
Map<String,TeamPO> teamPOMap = getTeamPOList(response.get("teams").getAsJsonArray(), playerMap);
List<MatchDTO> matchList = new ArrayList<>();
for (JsonElement match : matches) {
String startTime;
if (!match.getAsJsonObject().has("actual_start_time")) {
startTime="W/O";
matchList.add(new MatchDTO(null,startTime));
continue;
}
startTime = match.getAsJsonObject().get("actual_start_time").getAsString();
List<TeamDTO> teamList = new ArrayList<>();
for (JsonElement team : match.getAsJsonObject().get("teams").getAsJsonArray()) {
List<ScoreDTO> scoreList = new ArrayList<>();
for (JsonElement score : team.getAsJsonObject().get("score").getAsJsonArray()) {
String game = score.getAsJsonObject().get("game").getAsString();
String set = score.getAsJsonObject().get("set").getAsString();
Boolean winner = score.getAsJsonObject().get("winner").getAsBoolean();
scoreList.add(new ScoreDTO(set, game, winner));
}
String teamId = team.getAsJsonObject().get("team_id").getAsString();
Boolean winner = team.getAsJsonObject().has("status");
teamList.add(new TeamDTO(teamId, teamPOMap.get(teamId).getPlayers(), scoreList, winner));
}
matchList.add(new MatchDTO(teamList, startTime));
}
return matchList;
}
在项目进行的过程中,我发现对于players的数据常常需要进行多次解析,对运动员的id进行查找,而这个过程是十分耗时的!所以完成功能后我考虑用map的数据结构来对查找效率进行优化。改进的方法是:项目开始前获取playerlist,输出player时使用playerlist,保证输出顺序与接口顺序一致;获得playerlist后用该list创建playerMap,以运动员id为key,运动员实体为value,这样可以保证players.json文件只需要读取一次;在输出schedule时,解析json创建match时,需要使用运动员id查询运动员信息,利用hashmap高效的查询效率,将原本遍历查询时o(n)的时间复杂度压缩到o(logn)。利用teamid查询队伍信息时也使用map的方式,但因为队伍比较少所以优化不够明显。
结果可以看到,output.txt文件中输出数据有18674行,文件大小为297KB,在使用map数据结构后,程序运行时间从1045ms缩短到407ms,缩短了一倍多。(优化前的运行时间忘记截图了呜呜)
public class MyTest {
@Test
public void maintest() throws IOException {
String inputPath = "D:\\inputtext.txt";
String outputPath = "D:\\outputtext.txt";
AOSearch.main(new String[]{inputPath,outputPath});
String answerPath="D:\\rightoutput.txt";
Path pathTest = Paths.get(answerPath);
List<String> linesTests = Files.readAllLines(pathTest);
Path pathResult = Paths.get(outputPath);
List<String> linesResults = Files.readAllLines(pathResult);
Assert.assertEquals(linesTests,linesResults);
}
}
在main函数的input文件读取中或是解析json函数的json文件读取中,可能会出现无法找到文件,或是无法解析非json格式的文件等情况,通过throws IOException和catch(FileNotFoundException e)来输出异常发生时的提示。
一开始看到作业要求,感觉两眼一抹黑,连题目要求都看不懂。认真研读和分析半小时后才大概明白了思路,但是还是因为粗心没有看到群里助教的提示,助教已经给我们提供了解析好的json文件。我刚开始以为都要通过url请求从网络上直接获取数据,在程序功能完成之后,才发现这种方法对网络的要求很高,网络一差就出现运行超时的错误。到作业后期才改为本地json文件读取的方法,花费不少时间。所以以后我一定要及时关注软工实践课程群的消息!!但是我特别感谢助教的耐心解答和提示。在这次作业之前,我对java语言的掌握没有那么熟练,对json的结构和解析方法也只有简单的了解,同时对于单元测试Junit更是一无所知,也很少在算法类型的编程作业之外考虑过程序性能优化。而在实践之后,对java常用的对象和方法以及用Gson解析json的技术有了更深的了解和掌握,也学会了Junit的测试方法,发现程序的性能优化对编程来说是一个很重要、很有意义的部分。我认为这对我未来的项目开发将会有很大的帮助。
解题思路和设计实现过程写的很详细清晰,对问题的解决方法有自己的思考。做得很好,继续加油!