软件工程第二次作业--文件读取

这个作业属于哪个课程2302软件工程
这个作业的要求在哪里软件工程第二次作业–文件读取
这个作业的目标完成对世界游泳锦标赛相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序
其他参考文献《构建之法》《源代码管理》 《阿里巴巴代码规范》

1. gitcode项目地址

仓库地址

2. PSP表格

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

3. 解题思路描述

3.1 json文件的获取与解析

本次作业中我直接使用了助教提供的json文件,没有手动进行网络爬取。这里我将项目所需的所有json数据文件放到项目下,并进行了重命名以便后续的读取和解析操作。
数据文件

  • 对于功能一来说,我们需要获取所有运动员的信息,可以定位到athletes.json这个文件下。需要读取这个文件并解析这个文件,文件的结构如下图所示:

结构图
从json文件可以观察到,最外层是一个json数组,数组中的每个对象是每个国家的信息,其中键为CountryName对应的值是运动员的国家名称,再下一层可以看到键是Participations对应的值就是这个国家所有的参赛运动员组成的数组。对象中的PreferredFirstName键与PreferredFirstName键的值共同组成我们需要的Full Name信息。键是Gender的值对应运动员的性别,0为男,1为女,需要简单处理处理一下再输出。理清楚了文件结构和我们需要的信息后,就可以开始编码解析json数据了。

  • 对于功能二来说,我们需要解析是的指令对应的json文件,如result women 1m springboard 指令,则会去加载解析项目目录src下的women_1m_springboard.json文件,该文件的结构如下:

数据文件
文件结构1
可以观察到所有比赛 (包括初赛、半决赛、决赛) 的结果位于键为Heats的数组中,数组中是所有比赛的数据,其中键Name的值为Final的是总决赛的数据。定位到总决赛数据的对象后,可以观察到有键为Results的数组,这个数组就是总决赛的比赛结果数组。其中Rank对应的是选手的排名,FullName对应的是选手的全名,Dives对应的是得分情况的数组,数组中对象的DivePoint是单次得分情况。这些是我们需要获取并输出到文件中的数据。

在这两个功能的实现中,我使用谷歌的Gson库解析json文件,将json文件解析成json对象,然后将json对象转换成java的Bean对象,最后将java对象封装成List返回给调用者。

3.2 读取类路径下的文件

读取类路径下的文件
使用ClassLoader类加载器的getResourceAsStream来加载类路径下的文件,并把所有的命令以字符串集合的形式返回。

3.3 数据的封装

我在entity包下创建了Player和Result这两个实体类,分别用来封装运动员,决赛结果的信息。两个类都重写了对应的toString方法,可以输出题目要求格式的运动员和决赛结果的信息。

  • 运动员类
//运动员
public class Player {
    //选手全名
    private String fullName;
    //选手性别
    private String gender;
    //选手国籍
    private String country;

    public Player() {
    }
  ....setter 和 getter省略
    /**
     * 以要求的格式重写toString方法
     * @return 返回要求格式的字符串
     */
    @Override
    public String toString() {
        return "Full Name:" + fullName + '\n' +
                "Gender:" + gender + '\n' +
                "Country:" + country + '\n';
    }
}
  • 决赛结果类:
//决赛结果
public class Result {
    //选手全名
    private String fullName;
    //选手名次
    private String finalRank;
    //选手成绩
    private List<String> finalScore;

    ....构造函数和 setter 和 getter省略
    //将分数列表转化为需要的字符串
    private String toScoreString(List<String> scoreList) {
        if (scoreList == null) {
            return "*\n";
        }
        BigDecimal totalScore = scoreList.stream()
                .map(BigDecimal::new)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        String scoreString = scoreList.stream()
                .collect(Collectors.joining(" + ", "", " = " + totalScore + "\n"));
        return scoreString;
    }
   
    public String toString() {
        return
                "Full Name:" + fullName + "\n" +
                        "Rank:" + finalRank + "\n" +
                        "Score=" + toScoreString(finalScore);
    }

3.4 项目打包为jar包

直接使用idea来打包项目。先配置一下Aritifacts,设置好打包方式、包名、主类,以及jar包的输出路径。通过Build->Build Artifacts->Build进行打包。
在这里插入图片描述

4. 接口设计和实现过程

4.1 在Lib类中主要有三个接口

  1. public List<Player>getAllPlayers();

功能:负责获得所有的运动员信息,并且封装成含有Player对象的集合。
实现:通过JsonParseUtils工具类的parsePlayers方法获得Player对象的集合并返回,JsonParseUtils中根据传入的解析后的json文件的根对象JsonElement,根据前面分析的层级结构使用Gson解析出需要的数据,并做相应的封装。


  1. public List<Result>getFinalResults(String jsonFilePath);

功能:负责获得所有的决赛信息,并且封装成含有Result对象的集合
实现: 同理,该接口通过JsonParseUtils工具类的parseResults方法获得Result对象的集合并返回,JsonParseUtils中根据传入的解析后的json文件的根对象JsonElement来逐级提取提取需要的信息,并做相应的封装。


  1. public void executeCommand(String command, String outputFile);

功能:执行传入的命令和要输出的文件的路径,将执行命令的结果写入到对应的文件中。
实现:根据传入的命令进入不同的分支,执行不同的功能,调用上面不同的接口来获取数据,最后将数据以要求的格式的写入到对应路径下的文件中。


代码的独到之处就是使用了Gson来解析json文件,逐层提取所需信息并封装成Bean对象,最后转化好格式后向指定文件输出。

4.2 调用层级图

调用层级图

5. 关键代码展示

5.1获得全部运动员信息的接口代码展示:

   /**
     * 解析json数据封装成对象返回
     * @param jsonData Jason的Data
     * @return 返回运动员列表
     */
    public static List<Player> parsePlayers(JsonElement jsonData) {

        if (jsonData == null) return  null;

        List<Player> result = new ArrayList<>();
        JsonArray jsonArray = jsonData.getAsJsonArray();

        for (JsonElement jsonElement : jsonArray) {

            JsonObject countryWithPlayers = jsonElement.getAsJsonObject();
            String country = countryWithPlayers.get("CountryName").getAsString();
            JsonArray players = countryWithPlayers.getAsJsonArray("Participations");

            for (JsonElement playerElement : players) {
                JsonObject playerJson = playerElement.getAsJsonObject();
                Player player = new Player();
                String lastName = playerJson.get("PreferredLastName").getAsString();
                String firstName = playerJson.get("PreferredFirstName").getAsString();
                String fullName = lastName + " " + firstName;
                int gender = playerJson.get("Gender").getAsInt();
                player.setCountry(country);
                player.setFullName(fullName);
                player.setGender(gender == 0 ? "Male" : "Female");
                result.add(player);
            }
        }
        return result;
    }

5.1 获得所有决赛信息的接口代码展示:

    /**
     * 获取比赛结果
     * @param jsonData 解析后的第一个元素
     * @return 返回结果列表
     */
    public static List<Result> parseFinalResult(JsonElement jsonData) {

        if (jsonData == null) return null;

        JsonArray heats = jsonData.getAsJsonObject().getAsJsonArray("Heats");

        List<Result> resultList = new ArrayList<>();

        for (JsonElement heat : heats) {
            JsonObject eventResults = heat.getAsJsonObject();

            //说明是 FinalResult
            if ("Final".equals(eventResults.get("Name").getAsString())){

                JsonArray resultData = eventResults.getAsJsonArray("Results");

                for (JsonElement element : resultData) {

                    JsonObject player =  element.getAsJsonObject();
                    JsonArray dives = player.getAsJsonArray("Dives");
                    ArrayList<String> scoreList = new ArrayList<>();

                    for (JsonElement dive : dives) {
                        JsonObject diveObj =  dive.getAsJsonObject();
                        scoreList.add(diveObj.get("DivePoints").getAsString());
                    }
                    String fullName = player.get("FullName").getAsString();

                    Result result = new Result(fullName, player.get("Rank").getAsString(), scoreList);
                    resultList.add(result);
                }
            }
        }
        return  resultList;
    }

6. 性能改进

读取和解析json文件是一个很耗时的IO操作,这里为了简单,我直接使用了内存中的HashMap来缓存。当执行命令的时候,先以对应命令查询缓存,如果缓存命中,有对应的content则直接返回缓存中的数据,如果没有命中再去读取和解析json文件,并把解析后的内容设置到缓存中,大大减少了IO操作的耗时。
缓存工具类

7. 单元测试

使用junit来进行测试,编写测试类,设计测试单元,传入不同的参数,运行测试单元。
主要测试的就是Lib接口下的接口/功能,包括获取所有运动员信息、获取所有决赛的结果信息、执行不同的指令,向指定文件中输出结果的功能。测试类如下:

测试类
当运行测试test01时,测试执行指令功能,向对应文件输出运动员信息,覆盖率如下:
test01

当运行整个测试类( 10个测试用例 )的时候可以观察到,代码的覆盖率很高,符合要求:

测试全部代码

8. 异常处理

主要采用try…catch来进行异常处理,其中包括对文件的异常处理以及对数据流的异常处理。出现异常时,会在控制台有相应的错误提示信息,如读取的文件不存在,数据文件不存在,读取时出现错误等信息。

异常处理

9. 心得体会

通过这次作业,我不仅学会了如何解析复杂的JSON数据,还深入了解了Git和GitCode仓库在代码管理中的重要性。在处理复杂的JSON结构时,我需要仔细分析数据的层级关系和逻辑,确保能够正确地提取所需信息。在这个过程中,我逐渐掌握了处理复杂JSON数据的技巧,同时也意识到了规划和设计的重要性。
积极运用Git来管理代码也是这次作业的一部分。将代码提交到GitCode仓库不仅方便自己管理代码版本,还可以随时回溯之前的版本以及对比代码的变化,保证代码的可追溯性和可靠性。通过Git的使用,我也更加注重了代码的整洁性和规范性,因为良好的代码结构和规范能够提高代码的可读性和可维护性。
另外,我还学会了使用JUnit进行测试,这样可以帮助我验证代码的正确性,确保代码的质量和稳定性。通过编写测试用例和运行单元测试,我可以更加自信地对代码进行修改和重构。这也使得我的代码更加可靠,也为未来的项目开发奠定了良好的基础。
总的来说,通过这次作业,我不仅学会了处理复杂JSON数据,还深入了解了Git的使用和代码管理的重要性,同时也学会了使用JUnit进行测试,这些技能对我未来的项目开发都将大有裨益。同时,我也意识到在开始项目之前,应该对整体流程和所需工具有一个清晰的规划,这样可以避免后期频繁调整和修改代码带来的麻烦。及时记录和总结遇到的问题和解决方案也很重要,方便日后回顾和学习经验。

  • 16
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值