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

这个作业属于哪个课程 福州大学-202302软件工程实践
这个作业要求在哪里 软件工程第二次作业–文件读取
这个作业的目标完成对世界游泳锦标赛跳水项目相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序
其他参考文献构建之法、JUnit 4 超详细教程、码出高效_阿里巴巴Java开发手册IDEA单元测试–详细使用步骤


一、Gitcode项目地址

Gitcode项目地址


二、PSP表格

PSP StageDescriptionEstimated Time (minutes)Actual Time (minutes)
PlanningPlan3535
• Estimate• Estimate how long the task will take3030
DevelopmentDevelopment14001445
• Analysis• Requirement analysis (including learning new technologies)400350
• Design Spec• Generate design documentation3540
• Design Review• Design review5060
• Coding Standard• Coding standards (define appropriate standards for the current development)3030
• Design• Detailed design5565
• Coding• Coding520560
• Code Review• Code review4550
• Test• Testing (self-testing, code modifications, submitting changes)230270
ReportingReporting100110
• Test Report• Test report5560
• Size Measurement• Measure the amount of work3030
• Postmortem & Process Improvement Plan• Postmortem and propose process improvement plans2525
Total16451875

三、解题思路描述

3.1、获取数据

本次作业使用助教提供的相关数据,结构如下所示:

在这里插入图片描述

  • athletes:存储了关于跳水项目的运动员信息。
  • event:存储了项目名称项目id的对照,通过项目名称可以找到对应的项目id。
  • results:文件夹下存储以项目id命名的各个项目的成绩。包括总分与每一次比赛的小分。

3.2、数据处理

通过查阅网上资料,我选择使用Gson来解析数据:Gson 是一个由 Google 提供的用于在 Java 对象和 JSON 数据之间进行转换的库。它提供了简单易用的 API,可以方便地将 Java 对象序列化为 JSON 数据,也可以将 JSON 数据反序列化为 Java 对象。
我们可以通过gson.fromJson方法来将json数据转化为已定义的Java对象。Java类对象的定义如下图所示:
在这里插入图片描述

3.3、问题1

(1) 读取athletes.json文件

  • 首先定义文件的路径,利用BufferedReader对象读取json文件的内容并转换为字符串。

(2) 解析json文件中的数据

  • 由于athletes.json文件中是Country类的对象的数组,因此先通过gson.fromJson()提取出country对象,转换为country数组。
  • 然后依题意先按国籍进行排序,通过Arrays.sort();得到按国籍进行排序的country数组。再接着以LastName为次要关键字进行排序。

(3)返回解析完毕的字符串

  • 将排序完毕的信息拼接成字符串并返回。

3.4、问题2

(1) 解析输入命令

  • 首先读取并处理命令,解析命令result,过滤不合规的命令,构建命令对象,然后根据合法的命令对象中的比赛名称解析出相应比赛的ID。

(2) 构建路径并解析数据

  • 使用得到的比赛ID去构建文件路径,通过文件路径寻找到对应的json文件中去获取决赛结果信息。然后通过parseFinalEventResult方法得到所需的决赛结果。

(3)构建字符串并返回

  • 将决赛结果构建成符合输出要求的字符串并返回。

四、接口设计和实现过程

4.1 总体设计

Model类:根据Gson解析json数据的要求,构建相应的Java类,例如Country类、Command类、EventResult类等,并设计相应的属性字段。因为Gson 会自动将 JSON 对象中的字段名和 Java 类中的字段名进行匹配,如果匹配成功,Gson 就会将 JSON 对象中的值赋给 Java 类中的相应字段。如果 JSON 对象中的某个字段在 Java 类中没有对应的字段,那么 Gson 就会忽略这个字段。
Util类:本次作业设计了Lib类和CoreLib类,Lib类封装了大部分用来解析数据信息的函数,例如readAthletesInfoprocessResultCommand方法等。并用来控制相应的Model类。CoreLib类提供了输出运动员信息和输出决赛结果信息的接口。

4.2 核心部分实现过程

总体流程图:

在这里插入图片描述

4.3 算法的关键之处

数据读取机制:
模仿Cache的缓存机制,利用局部性原理设计算法,大大节省时间开销。
异常处理:
在读取文件、解析Json数据时加入异常处理机制,提高程序的健壮性。
接口封装:
将输出关键信息的功能封装成一个模块,便于组织和维护。
使用流操作:
流操作以声明性方式处理数据集合,在需要结果时才执行,提供了更好的性能控制。


五、关键代码展示

5.1 缓存部分

private static Map<String, String> cache = new HashMap<>();// 缓存数据的Map
// 如果数据在缓存中就直接获取数据,否则先缓存数据再返回数据
    public static String getCacheValue(Command command, Map<String, String> cache) throws IOException {
        String cacheKey = command.getCommandType().equals("result") ? command.getEventName() : command.getCommandType();
        if (cache.containsKey(cacheKey)) {// 如果缓存中有数据就直接返回
            return cache.get(cacheKey);
        } else {
            String cacheValue;
            switch (command.getCommandType()) {
                case "players":
                    cacheValue = Lib.readAthletesInfo();
                    break;
                case "result":
                    cacheValue = Lib.getFinalEventResult(command.getEventName());
                    break;
                case "N/A":
                    cacheValue = "N/A\n-----\n";
                    break;
                default:
                    cacheValue = "Error\n-----\n";
                    break;
            }

            cache.put(cacheKey, cacheValue);
            return cacheValue;
        }
    }

5.2 获取运动员信息

public static String readAthletesInfo() throws IOException {
        StringBuilder sb = new StringBuilder();
        // 读取JSON文件的内容
        try (BufferedReader reader = new BufferedReader(new FileReader(ATHLETES_FilePath))) {
            String str = reader.lines().collect(Collectors.joining());
            // 解析JSON数据
            Country[] countries = gson.fromJson(str, Country[].class);

            // 首先对国家进行排序,然后对运动员信息排序
            Arrays.sort(countries, Comparator.comparing(Country::getCountryName));
            String result = Arrays.stream(countries)
                    .flatMap(country -> sortAthletes(country).stream()
                            .map(athlete -> parseAthleteInfo(athlete, country.getCountryName())))
                    .collect(Collectors.joining());

            sb.append(result);
        }

        return sb.toString();
    }

六、性能改进

由于每次从文件中读取数据都需要耗费大量的时间,并且可能会读取相同的命令,如果每次都都执行相同的文件读写,会导致时间的巨大开销。而空间成本相对于时间成本较为低,因此便想到模仿Cache的缓存机制,当接收到命令时,先尝试从缓存中读取数据,如果缓存中没有数据,就先从文件中读取数据,并将数据缓存到Cache中,下次读取相同命令时可以直接从缓存中取出数据,大大节省了时间的开销。我经过查阅网上资料后,选择使用HashMap作为数据结构来实现缓存,原因如下:

  • HashMap具有快速查找的特性,我们可以将键作为输入的命令(cacheKey),将值作为文件中读取出来的内容(cacheValue)。

  • 每次接收到命令时,可以通过命令先查找HashMap中是否有缓存相关信息,如果有的话可以直接读取,不必进行文件读写;如果没有的话便先缓存到HashMap中,再对相关文件进行读写操作。
    关键代码如下所示:

private static Map<String, String> cache = new HashMap<>();// 缓存数据的Map

if (cache.containsKey(cacheKey)) {// 如果缓存中有数据就直接返回
            return cache.get(cacheKey);
        } else {// 否则便缓存数据
            String cacheValue;
            switch (command.getCommandType()) {
                case "players":
                    cacheValue = Lib.readAthletesInfo();
                    break;
                case "result":
                    cacheValue = Lib.getFinalEventResult(command.getEventName());
                    break;
                case "N/A":
                    cacheValue = "N/A\n-----\n";
                    break;
                default:
                    cacheValue = "Error\n-----\n";
                    break;
            }

            cache.put(cacheKey, cacheValue);
            return cacheValue;
        }

七、单元测试

在Java编程中,单元测试是确保代码质量和可靠性的重要环节。经过查阅网上资料,单元测试我选择使用JUnit模块,因为JUnit这一强大的单元测试框架不仅简化了测试用例的编写,还提供了便捷的测试执行机制。通过单元测试,我们能够有效地减少程序bug的产生。基于此,我构造的测试数据如下:
在这里插入图片描述

部分测试代码如下:
在这里插入图片描述

结果覆盖率如下:
在这里插入图片描述

代码覆盖率大部分都较高,符合预期。


八、异常处理

异常处理首先检验命令行参数输入是否符合标准,其次检查文件路径是否以txt结尾,符合要求后方可进行功能使用,代码如下所示:

public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("命令行参数个数输入错误!\n");
            return;
        } else {
            // 检查参数是否以.txt结尾
            if (!args[0].endsWith(".txt") || !args[1].endsWith(".txt")) {
                System.out.println("命令行参数必须是以.txt结尾的文件路径!");
                return;
            }
            String inputFile = args[0];
            String outputFile = args[1];

            try {
                CoreLib.writeToFile(inputFile, outputFile);
            } catch (IOException e) {
                System.out.println("文件不存在:" + e.getMessage());
            }
        }
    }

九、心得体会

通过本次作业,我不仅体会到了项目性能优化的重要性,还学习到了如何合理地规划时间去完成一个项目,逐步推进去完成它。我还学习到了用缓存去读取数据,如果按照以往,我大概会是实现了从文件读取数据之后就认为大功告成了,但是想要更好的运行体验,就还需要采用优化的手段来改善我们的程序。我还学会了用git来及时地签入项目、管理项目,并且学会使用Maven来搭建项目结构、引入jar包,还学习了java中处理json数据的工具包,总的来说收获颇丰。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值