软件工程实践第二次作业——个人实战

222000234张程越 学生 2023-03-02 22:59:46
这个作业属于哪个课程2023春季软件工程实践-W班(福州大学)
这个作业要求在哪里软件工程实践第二次作业——个人实战
这个作业的目标完成对澳大利亚网球公开赛相关数据的收集、掌握json数据的获取和解析、掌握java文件的输入输出、掌握java项目打jar包的方法
其他参考文献JAVA之单元测试:Junit框架软件测试开发实战|Java版本,Gson解析json比较常用的2种方式

目录

  • 1.Gitcode项目地址
  • 2.PSP表格
  • 3.解题思路
  • 3.1 如何实现input.txt文件内容输入和将打印字符输出到output.txt文件
  • 3.2 如何获取json数据
  • 3.3 如何解析json数据
  • 4.设计实现过程
  • 4.1输入输出实现
  • 4.2 对input.txt输入内容进行判断
  • 4.3 对json数据进行解析并将信息存入实体类后输出
  • 5.关键代码展示
  • 6.性能改进
  • 7.单元测试
  • 7.1 测试代码
  • 7.2 覆盖率
  • 8.异常处理
  • 9.心得体会

1.Gitcode项目地址

Chyu__/Project-Java

2.PSP表格

PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划3030
  • Estimate
  • 估计这个任务需要多少时间
3030
Development开发12701115
  • Analysis
  • 需求分析 (包括学习新技术)
180160
  • Design Spec
  • 生成设计文档
3020
  • Design Review
  • 设计复审
2015
  • Coding Standard
  • 代码规范(为目前的开发制定合适的规范)
2030
  • Design
  • 具体设计
120120
  • Coding
  • 具体编码
720640
  • Code Review
  • 代码复审
6050
  • Test
  • 测试(自我测试,修改代码,提交修改)
12080
Reporting报告110110
  • Test Report
  • 测试报告
6050
  • Size Measurement
  • 计算工作量
3030
  • Postmortem & Process Improvement Plan
  • 事后总结, 并提出过程改进计划
2030
合计14101255

3.解题思路

3.1 如何实现input.txt文件内容输入和将打印字符输出到output.txt文件

在main函数参数String[] args中,将intput.txt文件的路径赋值给args[0],output.txt文件的路径赋值给args[1]。通过Files.readAllLines()方法来获取input.txt文件中的每一行,再将每一行的内容传入自定义的一个内容分析方法进行判断;通过PrintStream对象的构造函数可以将System.out.println()的打印内容输出到output.txt。

3.2 如何获取json数据

在我最开始读完作业要求后,因为粗心没有看见助教已经提供了部分json数据,我错误地认为所有的json数据都要通过http从澳网的网站上获取。所以我最开始先寻找关于java的okhttp是如何使用的一些参考资料。

(1)通过url请求获取数据:

在澳网的官网中点击schedule模块(后续因为助教更新的json数据是从result模块的接口拿到的数据,所以我后来接口也做了更改,但获得URL请求的方法相同),键盘按下F12打开开发者工具,在开发者工具中点击网络(Network),再点击Fetch/XHR,刷新,可以看到开发者工具里列出了页面的请求列表,找到schedule,点击右侧Headers,在General区域中就可以看到请求的url(Request URL)。(图示以Day14为例)

img

对url的处理:

我新建了一个类叫UrlList,分别用来存放players、Q、Day这三类请求的url,但对于Day和Q的URL请求需要做一些处理,因为Day和Q有多个不同日期。

img

用*代替天数所在的位置,后续只需要用字符串替换的方法将对应的天数把"*"号替换掉就行。

最后通过URL对象和HttpURLConnection对象就可以读取到相应json数据。

(2)通过复制Response获得json数据

在完成所有功能之后我发现用URL请求json数据的方法在运行时很受网络状况的影响,于是我决定改成使用本地json文件来实现。

数据的获取与之前差别不大,只需要将点击Header那步改为点击Response,就可以看到该接口的json数据,将其复制下来,格式化后贴入自己在data文件夹下新建的空白json文件中即可。

img

3.3 如何解析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();
    }

4.设计实现过程

图片名称
4.1输入输出实现

​ 在AOSearch类中,我通过参数args和PrintStream对象实现了input.txt文件内容的输入和将打印结果输出到文件output.txt。创建了一个私有变量playerList来存放获取到的所有选手信息,获取方法是Lib接口中的getPlayersMap()。将input.txt的内容和playerList作为参数传入Lib接口的contentIdentify()方法中对输入内容进行分析。

4.2 对input.txt输入内容进行判断

​ 在contentIdentify()方法中创建一个list变量来存放每一行用空格分隔开的内容。如果该行第一个参数和字符串“players”一样,且该行通过空格分隔后的list只有一个元素,则该行的命令为让我们输出选手信息,调用outputPlayer()方法;如果该行第一个参数为result,且该行通过空格分隔后的list有两个元素,则调用resultIdentify()方法进入下一步对日期和赛季的判断。

​ 在resultIdentify()中,我将list元素第二个参数与“0116”······“”0129进行比对,如果与某个日期相同,调用outputDay()方法输出当日比赛结果;与”Q1“······”Q4“进行比对,如果与某个赛季相同,则调用outputQuarter()方法输出该赛季比赛结果。

4.3 对json数据进行解析并将信息存入实体类后输出

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);

5.关键代码展示

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;
}

6.性能改进

​ 在项目进行的过程中,我发现对于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,缩短了一倍多。(优化前的运行时间忘记截图了呜呜)

图片名称

7.单元测试

7.1 测试代码
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);
    }
}
图片名称
7.2 覆盖率
图片名称

8.异常处理

​ 在main函数的input文件读取中或是解析json函数的json文件读取中,可能会出现无法找到文件,或是无法解析非json格式的文件等情况,通过throws IOException和catch(FileNotFoundException e)来输出异常发生时的提示。


9.心得体会

​ 一开始看到作业要求,感觉两眼一抹黑,连题目要求都看不懂。认真研读和分析半小时后才大概明白了思路,但是还是因为粗心没有看到群里助教的提示,助教已经给我们提供了解析好的json文件。我刚开始以为都要通过url请求从网络上直接获取数据,在程序功能完成之后,才发现这种方法对网络的要求很高,网络一差就出现运行超时的错误。到作业后期才改为本地json文件读取的方法,花费不少时间。所以以后我一定要及时关注软工实践课程群的消息!!但是我特别感谢助教的耐心解答和提示。在这次作业之前,我对java语言的掌握没有那么熟练,对json的结构和解析方法也只有简单的了解,同时对于单元测试Junit更是一无所知,也很少在算法类型的编程作业之外考虑过程序性能优化。而在实践之后,对java常用的对象和方法以及用Gson解析json的技术有了更深的了解和掌握,也学会了Junit的测试方法,发现程序的性能优化对编程来说是一个很重要、很有意义的部分。我认为这对我未来的项目开发将会有很大的帮助。

...全文
443 2 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
2 条回复
切换为时间正序
请发表友善的回复…
发表回复
aboutazhang 2023-03-14
  • 打赏
  • 举报
回复

解题思路和设计实现过程写的很详细清晰,对问题的解决方法有自己的思考。做得很好,继续加油!

222000234张程越 学生 2023-03-23
  • 举报
回复
@aboutazhang 谢谢助教!

686

社区成员

发帖
与我相关
我的任务
社区描述
2023年福州大学软件工程实践课程W班的教学社区
软件工程团队开发软件构建 高校 福建省·福州市
社区管理员
  • FZU_SE_teacherW
  • aboutazhang
  • 郭渊伟
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧