122
社区成员
发帖
与我相关
我的任务
分享| 这个作业属于哪个课程 | 福州大学-202302软件工程实践 |
|---|---|
| 这个作业要求在哪里 | 软件工程第二次作业--文件读取 |
| 这个作业的目标 | 完成对世界游泳锦标赛跳水项目相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序 |
| 其他参考文献 | 构建之法、JUnit 4 超详细教程、码出高效_阿里巴巴Java开发手册、IDEA单元测试--详细使用步骤 |
| PSP Stage | Description | Estimated Time (minutes) | Actual Time (minutes) |
|---|---|---|---|
| Planning | Plan | 35 | 35 |
| • Estimate | • Estimate how long the task will take | 30 | 30 |
| Development | Development | 1400 | 1445 |
| • Analysis | • Requirement analysis (including learning new technologies) | 400 | 350 |
| • Design Spec | • Generate design documentation | 35 | 40 |
| • Design Review | • Design review | 50 | 60 |
| • Coding Standard | • Coding standards (define appropriate standards for the current development) | 30 | 30 |
| • Design | • Detailed design | 55 | 65 |
| • Coding | • Coding | 520 | 560 |
| • Code Review | • Code review | 45 | 50 |
| • Test | • Testing (self-testing, code modifications, submitting changes) | 230 | 270 |
| Reporting | Reporting | 100 | 110 |
| • Test Report | • Test report | 55 | 60 |
| • Size Measurement | • Measure the amount of work | 30 | 30 |
| • Postmortem & Process Improvement Plan | • Postmortem and propose process improvement plans | 25 | 25 |
| Total | 1645 | 1875 |
本次作业使用助教提供的相关数据,结构如下所示:
通过查阅网上资料,我选择使用Gson来解析数据:Gson 是一个由 Google 提供的用于在 Java 对象和 JSON 数据之间进行转换的库。它提供了简单易用的 API,可以方便地将 Java 对象序列化为 JSON 数据,也可以将 JSON 数据反序列化为 Java 对象。
我们可以通过gson.fromJson方法来将json数据转化为已定义的Java对象。Java类对象的定义如下图所示:
(1) 读取athletes.json文件
(2) 解析json文件中的数据
gson.fromJson()提取出country对象,转换为country数组。Arrays.sort();得到按国籍进行排序的country数组。再接着以LastName为次要关键字进行排序。(3)返回解析完毕的字符串
(1) 解析输入命令
(2) 构建路径并解析数据
parseFinalEventResult方法得到所需的决赛结果。(3)构建字符串并返回
Model类:根据Gson解析json数据的要求,构建相应的Java类,例如Country类、Command类、EventResult类等,并设计相应的属性字段。因为Gson 会自动将 JSON 对象中的字段名和 Java 类中的字段名进行匹配,如果匹配成功,Gson 就会将 JSON 对象中的值赋给 Java 类中的相应字段。如果 JSON 对象中的某个字段在 Java 类中没有对应的字段,那么 Gson 就会忽略这个字段。
Util类:本次作业设计了Lib类和CoreLib类,Lib类封装了大部分用来解析数据信息的函数,例如readAthletesInfo、processResultCommand方法等。并用来控制相应的Model类。CoreLib类提供了输出运动员信息和输出决赛结果信息的接口。

数据读取机制:
模仿Cache的缓存机制,利用局部性原理设计算法,大大节省时间开销。
异常处理:
在读取文件、解析Json数据时加入异常处理机制,提高程序的健壮性。
接口封装:
将输出关键信息的功能封装成一个模块,便于组织和维护。
使用流操作:
流操作以声明性方式处理数据集合,在需要结果时才执行,提供了更好的性能控制。
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;
}
}
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数据的工具包,总的来说收获颇丰。
可能由于IDE较为老旧的原因,资源文件无法准确定位。我在自己使用命令“Java -jar DWASearch.jar input.txt output.txt”来测试DWASearch.jar是否可以正常运行时,运行程序无法找到data文件所在。谨慎起见,我在src文件夹中和jar包同目录的位置都添加了data文件夹,最终程序可以正常运行。如图所示:
