这个作业属于哪个课程 | 2302软件工程 |
---|---|
这个作业要求在哪里 | 软件工程第二次作业–文件读取 |
这个作业的目标 | 完成对世界游泳锦标赛跳水项目相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序 |
其他参考文献 | 《构建之法》、《阿里巴巴Java开发手册》、《单元测试和回归测试》 |
Catalog
gitcode项目地址
PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 20 |
• Estimate | • 估计这个任务需要多少时间 | 10 | 20 |
Development | 开发 | 820 | 1130 |
• Analysis | • 需求分析 (包括学习新技术) | 120 | 70 |
• Design Spec | • 生成设计文档 | 30 | 100 |
• Design Review | • 设计复审 | 20 | 50 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
• Design | • 具体设计 | 40 | 60 |
• Coding | • 具体编码 | 360 | 480 |
• Code Review | • 代码复审 | 120 | 180 |
• Test | • 测试(自我测试,修改代码,提交修改) | 120 | 180 |
Reporting | 报告 | 120 | 260 |
• Test Repor | • 测试报告 | 60 | 150 |
• Size Measurement | • 计算工作量 | 10 | 30 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 50 | 80 |
合计 | 950 | 1410 |
解题思路描述
总体思路
首先,要从网站上获取数据,通常是json数据文件
其次,获取数据后要使得数据变得更容易处理
再次,处理完数据后,要使用json的分析工具,解析json数据
最后,处理解析后的json数据,按题目要求,根据指令,输出结果
获取网站数据
利用开发者工具
从网站的网络请求手动获得我们需要的数据.json文件
根据网站的events.json里对应ID
可以获得各个运动项目的结果的.json文件
也可以获得运动员数据athlete.json
获得完数据后将其保存好,进行下一步处理
处理网站数据
为了更好利用程序的输入指令
因此将获取的数据对照event.json用ID对应比赛项目的名称
重新命名,以便更好的使用数据
利用fastjson处理json数据
利用Maven管理项目,导入fastjson包,对得到的json数据进行处理
关键代码如下(非完整代码):
在maven项目中的pom.xml里导入fastjson包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.9.graal</version>
</dependency>
处理json数据的例子
JSONArray heats= parseJsonInputStream(inputStream);
JSONObject heat = heats.getJSONObject(0);
JSONArray results = heat.getJSONArray("Results");
for (Object resultObj : results) {
JSONObject result = (JSONObject) resultObj;
String fullName=result.getString("FullName");
if(fullName.contains(" / ")){
outputSynchronizedSortedName(fullName,writer);
}
else{
//写入
}
//写入
StringBuilder scores = new StringBuilder("Score:");
JSONArray dives = result.getJSONArray("Dives");
double totalPoints = 0;
for (Object obj : dives) {
JSONObject dive = (JSONObject) obj;
scores.append(dive.getString("DivePoints"));
totalPoints += Double.parseDouble(dive.getString("DivePoints"));
if (dives.indexOf(obj) < dives.size() - 1) {
scores.append(" + ");
}
}
//写入
}
接口设计和实现过程
由于实现的功能较少,我并没有划分具体的软件包
项目通过四个类实现,分别是
DWASearch.class
DivingResult.class
Athlete.class
AthleteDataParser.class
DivingResult类是存储比赛结果的数据结构
Athlete类是存储运动员信息的数据结构
AthleteDataParser类是对运动员信息进行解析的功能类
DWASearch类是程序的驱动类,用于分析输入的文件指令,输出相对应的结果
DWASearch类中还囊括了对比赛结果的解析
关键代码展示
DWASearch类,读取指令,执行指令
/**
* 读取文件里的指令
* @param fileName 文件名
* @return 读取到的指令作为字符串列表
*/
private static List<String> readCommandsFromFile(String fileName) {
List<String> commands = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
while ((line = reader.readLine()) != null) {
commands.add(line);
}
} catch (IOException e) {
System.out.println("Error reading input file: " + e.getMessage());
return null;
}
return commands;
}
/**
* 执行指令
* @param command 读取到的指令
* @param writer 缓存写入器
*/
private static void executeCommand(String command, BufferedWriter writer)
throws IOException {
if (command.startsWith("result ")) {
if(command.endsWith(" detail")){
outputDetailResults(getDetailedResultsMap(command,writer), writer);
}
else
outputFinalResults(command, writer);
} else if (command.equals("players")) {
outputPlayersInfo(writer);
} else {
writer.write("Error\n-----\n");
}
}
DWASearch类,AthleteDataParser类关键代码:
1.处理运动员信息
2.写入,更新详细比赛结果
/**
*
* @param jsonFilePath json文件路径
* @return 运动员列表
*/
public static List<Athlete> parseAthleteData(String jsonFilePath) {
List<Athlete> athletes = new ArrayList<>();
try (InputStream is = AthleteDataParser.class.getResourceAsStream(jsonFilePath)) {
if (is == null) {
return null;
}
byte[] data = is.readAllBytes();
// 读取JSON文件内容为字符串
String jsonStr = new String(data, StandardCharsets.UTF_8);
JSONArray countries = JSON.parseArray(jsonStr);
for (Object countryObj : countries) {
JSONObject country = (JSONObject) countryObj;
JSONArray participations = country.getJSONArray("Participations");
for (Object participationObj : participations) {
JSONObject participation = (JSONObject) participationObj;
Athlete athlete = new Athlete();
athlete.setPreferredLastName(participation.getString("PreferredLastName"));
athlete.setPreferredFirstName(participation.getString("PreferredFirstName"));
athlete.setNAT(participation.getString("NAT"));
athlete.setGender(participation.getIntValue("Gender"));
athletes.add(athlete);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return athletes;
}
/**
* 获得详细结果映射表
* @param command 读取到的指令
* @param writer 缓存写入器
* @return 运动员名字与比赛结果的一对一映射表
*/
private static Map<String, DivingResult> getDetailedResultsMap(String command, BufferedWriter writer)
throws IOException {
DivingResult d = new DivingResult();
String fileName = command.replace("result ", "")
.replace(" detail", "") + ".json";
String filePath="/data/results/" + fileName;
InputStream inputStream=d.getClass().getResourceAsStream(filePath);
if(inputStream==null){
writer.write("N/A\n-----\n");
return null;
}
Map<String, DivingResult> resultMapping = new HashMap<>();
JSONArray heats= parseJsonInputStream(inputStream);
for (Object heatObj : heats) {
JSONObject heat = (JSONObject) heatObj;
JSONArray results = heat.getJSONArray("Results");
for (Object resultObj : results) {
JSONObject resultJson = (JSONObject) resultObj;
String playerKey = resultJson.getString("FullName");
DivingResult divingResult = resultMapping.getOrDefault(playerKey, new DivingResult());
updateDetailResult(divingResult, resultJson, heat);
resultMapping.put(playerKey, divingResult);
}
}
return resultMapping;
}
/**
* 更新比赛结果映射表
* @param divingResult 已知的比赛结果
* @param resultJson 结果的Json对象
* @param heat 比赛的Json对象
*/
private static void updateDetailResult(DivingResult divingResult, JSONObject resultJson, JSONObject heat) {
String heatName = heat.getString("Name");
switch (heatName) {
case "Final" -> {
divingResult.setFinalRank(resultJson.getInteger("Rank"));
divingResult.setFinalScores(getResultScores(resultJson));
}
case "Semifinal" -> {
divingResult.setSemiRank(resultJson.getInteger("Rank"));
divingResult.setSemiScores(getResultScores(resultJson));
}
default -> {
divingResult.setPrelimRank(resultJson.getInteger("Rank"));
divingResult.setPrelimScores(getResultScores(resultJson));
}
}
divingResult.setFullName(resultJson.getString("FullName"));
}
思路解释:通过解析json文件,获取json数组,通过两个模型类来存储信息,用FileWriter来输出到对应的输出文件
独到之处:
1.利用HashMap来存储详细比赛结果,实现比赛结果与选手的一对一映射表,实现快速存取排序。
2.代码复用率高,简洁明了。
3.利用处理过后的文件名,可以根据指令简单获取到相应的数据。
性能改进
使用BufferReader & BufferWriter缓冲存储,简化代码逻辑
1.代码中的Writer与Reader均使用缓存存取
在读取和写入文件时使用缓冲流,减少IO操作,提高读写效率。
2.简化了switch,以及几个函数的代码逻辑
效果如下:
输入players指令优化前后:
优化前:94ms
优化后:94ms->76ms
输入多行Final Result指令:
优化前:165ms
优化后:165ms->147ms
输入多行Detail Result
优化前:198ms
优化后:198ms->169ms
可见性能的提升还是比较可观的
单元测试
设计样例代码
单元测试设计样例如下:
@Test
public void testNoParams() {
//错误样例-没有参数
DWASearch.main(new String[]{});
// Verify the results
}
@Test
public void testOneParams() {
//错误样例-参数太少
DWASearch.main(new String[]{"input.txt"});
// Verify the results
}
@Test
public void testManyParams() {
//错误样例-参数太多
DWASearch.main(new String[]{"input.txt","output.txt","wow.txt"});
// Verify the results
}
@Test
public void testWrongParams() {
// 错误样例-参数输入错误
DWASearch.main(new String[]{"input"});
// Verify the results
}
@Test
public void testFileNotFound() {
// 错误样例-找不到文件
DWASearch.main(new String[]{"args.txt","output.txt"});
// Verify the results
}
@Test
public void testCorrectWithNoCommand() {
// 正确样例-无指令
try (BufferedWriter writer = new BufferedWriter(new FileWriter("input.txt"))) {
// 写入空字符串来清空文件内容
writer.write("");
} catch (IOException e) {
System.out.println("Error writing to input file: " + e.getMessage());
}
DWASearch.main(new String[]{"input.txt","output.txt"});
// Verify the results
}
@Test
public void testCorrectWithFinalResult() {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("input.txt"))) {
// 写入空字符串来清空文件内容
writer.write("");
writer.write("result women 1m springboard");
} catch (IOException e) {
System.out.println("Error writing to input file: " + e.getMessage());
}
// 正确样例-指令:result women 1m springboard
DWASearch.main(new String[]{"input.txt","output.txt"});
// Verify the results
}
@Test
public void testCorrectWithDetailResult() {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("input.txt"))) {
// 写入空字符串来清空文件内容
writer.write("");
writer.write("result women 3m synchronised detail");
} catch (IOException e) {
System.out.println("Error writing to input file: " + e.getMessage());
}
// 正确样例-指令:result women 3m synchronised detail
DWASearch.main(new String[]{"input.txt","output.txt"});
// Verify the results
}
@Test
public void testCorrectWithMultiLineCommand() {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("input.txt"))) {
// 写入空字符串来清空文件内容
writer.write("");
writer.write(
"""
player
Players
resultwomen 1m springboard
result women 10m springboard
result sss
result detail
result women 1m springboard details
result men 10m synchronised
players""");
} catch (IOException e) {
System.out.println("Error writing to input file: " + e.getMessage());
}
// 正确命令行输入样例-复杂多行指令
DWASearch.main(new String[]{"input.txt","output.txt"});
// Verify the results
}
@Test
public void testCorrectWithPlayers() {
//
try (BufferedWriter writer = new BufferedWriter(new FileWriter("input.txt"))) {
// 写入空字符串来清空文件内容
writer.write("");
writer.write("players");
} catch (IOException e) {
System.out.println("Error writing to input file: " + e.getMessage());
}
// 正确样例-指令:players
DWASearch.main(new String[]{"input.txt","output.txt"});
// Verify the results
}
@Test
public void testCorrectWithMultiLineFinalCommand() {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("input.txt"))) {
// 写入空字符串来清空文件内容
writer.write("");
writer.write(
"""
result women 1m springboard
result women 3m springboard
result women 10m platform
result women 3m synchronised
result women 10m synchronised
result men 1m springboard
result men 3m springboard
result men 10m platform
result men 3m synchronised
result men 10m synchronised""");
} catch (IOException e) {
System.out.println("Error writing to input file: " + e.getMessage());
}
// 正确命令行输入样例-复杂多行指令
DWASearch.main(new String[]{"input.txt","output.txt"});
// Verify the results
}
@Test
public void testCorrectWithMultiLineDetailCommand() {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("input.txt"))) {
// 写入空字符串来清空文件内容
writer.write("");
writer.write(
"""
result women 1m springboard detail
result women 3m springboard detail
result women 10m platform detail
result women 3m synchronised detail
result women 10m synchronised detail
result men 1m springboard detail
result men 3m springboard detail
result men 10m platform detail
result men 3m synchronised detail
result men 10m synchronised detail""");
} catch (IOException e) {
System.out.println("Error writing to input file: " + e.getMessage());
}
// 正确命令行输入样例-复杂多行指令
DWASearch.main(new String[]{"input.txt","output.txt"});
// Verify the results
}
自动化测试与覆盖率
通过使用SquareTest的idea插件可以实现自动化测试,设计测试样例
覆盖率较高
对错误输入也有存在异常处理,输出错误的文本
异常处理
我的程序对基本上所有可能存在错误输入的参数以及所有可能存在的空指针进行异常处理:
通常为输出错误提示信息或文本
例子如下:
if (athletes != null) {
for (Athlete athlete : athletes) {
writeAthleteInfo(writer, athlete);
}
System.out.println("Successfully wrote athlete information to output file.");
}
else{
writer.write("N/A\n-----\n");
}
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
while ((line = reader.readLine()) != null) {
commands.add(line);
}
} catch (IOException e) {
System.out.println("Error reading input file: " + e.getMessage());
return null;
}
心得体会
通过本次作业
我学习到了以下内容:
1.使用Maven管理项目,通过maven来增加项目依赖
2.使用SquareTest插件来进行项目的单元测试
3.使用fastjson工具来解析并处理json文件
4.懂得使用缓存写入读取
5.大致了解了软件项目提交git管理的流程与做法,体会了项目管理
6.本次项目给我了莫大的信心,克服了来自ddl的恐惧,逐步推进完成项目,自信心增强不少。
请多多批评指教,感谢!
Sat Mar 02 2024 17:45:35 GMT+0800.