111
社区成员




这个作业属于哪个课程 | 我们的软件工程社区 |
---|---|
这个作业要求在哪里 | 软件工程实践第二次作业 |
这个作业的目标 | 完成对2024年巴黎奥运会相关数据的收集,并实现一个能够对国家排名及奖牌个数统计的控制台程序。 |
其他参考文献 | 无 |
##目录 | |
目录
|
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 35 |
• Estimate | • 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 600 | 800 |
• Analysis | • 需求分析 (包括学习新技术) | 120 | 180 |
• Design Spec | • 生成设计文档 | 30 | 30 |
• Design Review | • 设计复审 | 20 | 40 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 20 | 15 |
• Design | • 具体设计 | 40 | 40 |
• Coding | • 具体编码 | 800 | 960 |
• Code Review | • 代码复审 | 20 | 30 |
• Test | • 测试(自我测试,修改代码,提交修改) | 60 | 60 |
Reporting | 报告 | 20 | 20 |
• Test Repor | • 测试报告 | 10 | 15 |
• Size Measurement | • 计算工作量 | 10 | 10 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 30 | 20 |
合计 | 1810 | 2650 |
因为JAVA皆对象,所以如果能把网页爬取的数据转化为Java的对象的话,文件的读入和输出就不成问题
对于JAVA来说,json文件是比较常用的数据,通过依赖,使用FastJson中的函数,可以做到读入json文件并且输出json文件
我们从代码的功能先分析,首先我们要获得信息,那么我们就需要爬取2024巴黎奥运会的数据,要怎么获取数据呢,以获取每个国家的奖牌信息为例子,我的方法是在网页中点击“检查”
本人因为自己实力不太行,爬出来的数据奇奇怪怪的,有些日期和时间甚至是分开的,所以我先在excel表格中用CONTACT函数将日期和具体比赛时间拼接在一起,然后再把处理过的excel表格转换为对应的Json文件(本人未学爬虫,只能用这种笨办法了,阴间的数据结构请老大家谅解)。
medals.json的部分数据如下
{
"total": 91,
"medalsList": [
{
"bronze": "42",
"rank": "1",
"count": "126",
"silver": "44",
"countryname": "美国",
"gold": "40",
"countryid": "USA"
},
{
"bronze": "24",
"rank": "2",
"count": "91",
"silver": "27",
"countryname": "中国",
"gold": "40",
"countryid": "CHN"
},
{
-----
rank1:string
gold:number
silver:number
bronze:number
total:number
-----
为了将json数据转化为java对象
先建一个Medal类
public static class Medal{
public String gold;
public String silver;
public String bronze;
public String count;
public String countryname;
public String countryid;
public String rank;
}
-----
对于json对象的读取和输出,我们分别创建两个函数(我认为这真的是个一劳永逸的功能,同时也是单元测试的重点)
/// 读入文件内容函数
private static String readJsonFromFile(String filePath) throws IOException {
try (BufferedReader jsonReader = new BufferedReader(new FileReader(filePath, StandardCharsets.UTF_8))) {
StringBuilder jsonStr = new StringBuilder();
String line;
while ((line = jsonReader.readLine()) != null) {
jsonStr.append(line);
}
return jsonStr.toString();
}
}
这段代码的作用是读取指定路径的文件,将文件的全部内容作为一个字符串返回。简单解说一下:
public class Write_File {
// 输出内容至文件函数
public static void writeToFile(String filePath, String content) throws IOException {
try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(filePath, true), "UTF-8")) {
writer.write(content);
}
}
}//采用追加并且以UTF-8的编码格式输出
在这里要强调一点,我们的OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(filePath, true), "UTF-8"))
中,我们可以兼顾多个输入后文本文件的追加,又使用UTF-8格式进行输出。public class Deal_Command {
public static void Deal_command(String command, String OUT_TXT) throws IOException {
// 检查命令是否有效
if (command == null || command.trim().isEmpty()) {
handleInvalidCommand(OUT_TXT);
return;
}
String trimmedCommand = command.trim();
if ("total".equalsIgnoreCase(trimmedCommand)) {
Deal_Total.Total(trimmedCommand, OUT_TXT);
} else if (trimmedCommand.startsWith("schedule")) {
Deal_Schedule.Schedule(trimmedCommand, OUT_TXT);
} else {
handleInvalidCommand(OUT_TXT);
}
}
private static void handleInvalidCommand(String OUT_TXT) throws IOException {
StringBuilder output = new StringBuilder();
output.append("Error\n");
output.append("-----\n");
System.out.println("Error");
Write_File.writeToFile(OUT_TXT, output.toString());
}
}
可读取input.txt的多行命令
### 功能1:输出所有国家信息
</span>
**读取 JSON 数据:**
```javascript
String jsonStr = Read_File.readJsonFromFile("src/data/medals.json");//将JSON内容转化为JAVA对象
使用 Read_File.readJsonFromFile
方法从 "src/data/medals.json"
文件中读取 JSON 数据。
将读取到的 JSON 字符串解析成 JSONObject 对象。
从这个 JSONObject 中提取 medalsList 数组。
处理数据:
将 medalsList 中的每个 JSONObject 添加到一个 List 中。
按照每个 JSONObject 的 "rank" 值对列表进行排序。 medals.sort(Comparator.comparingInt(o -> o.getInteger("rank")));
用于排序
JSONObject root = JSONObject.parseObject(jsonStr);
JSONArray list = root.getJSONArray("medalsList");
List<JSONObject> medals = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
medals.add(list.getJSONObject(i));
}
medals.sort(Comparator.comparingInt(o -> o.getInteger("rank")));
生成输出字符串:
遍历排序后的列表,将每个对象的排名、国家、金牌、银牌、铜牌和总数格式化为字符串。
将这些字符串拼接在一起,以便写入到输出文件中。
根据作业输出样例进行字符串的组装
StringBuilder output = new StringBuilder();//输出字符串
for (JSONObject obj : medals) {
String rank = obj.getString("rank");
int gold = obj.getInteger("gold");
int silver = obj.getInteger("silver");
int bronze = obj.getInteger("bronze");
int total = gold + silver + bronze;
output.append("rank: ").append(rank).append(": ").append(obj.getString("countryid")).append("\n");
output.append("gold: ").append(gold).append("\n");
output.append("silver: ").append(silver).append("\n");
output.append("bronze: ").append(bronze).append("\n");
output.append("total: ").append(total).append("\n");
output.append("-----\n");
}
Write_File.writeToFile(OUT_TXT, output.toString());
}
使用 Write_File.writeToFile 方法将生成的字符串写入到指定的输出文件 OUT_TXT。
错误处理:
catch (IOException e) {
System.err.println("Error reading/writing file: " + e.getMessage());
} catch (Exception e) {
System.err.println("Error processing JSON: " + e.getMessage());
}
像我这种菜鸟一般就是从结果推出过程。
String[] parts = command.split(" ");// 分割指令schedule 和日期
if (parts.length != 2) {
System.out.println("Invalid command format. Use 'schedule MMDD'.");
return;
}
String date = parts[1];//取分割后段的日期字符
读取数据:
从 src/data/sports.json 文件中读取 JSON 数据。
将 JSON 数据解析为 JSONObject 对象,并从中提取 sportsList 数组。
// 将Json中的赛程信息导入
String jsonStr = Read_File.readJsonFromFile("src/data/sports.json");
JSONObject root = JSONObject.parseObject(jsonStr);
JSONArray list = root.getJSONArray("sportsList");
StringBuilder output = new StringBuilder();
SimpleDateFormat inputDateFormat = new SimpleDateFormat("M月d日HH:mm");//设置日期输入格式
SimpleDateFormat outputDateFormat = new SimpleDateFormat("HH:mm");//设置日期输出格式(为了符合样例输出)
输出方式
创建一个 StringBuilder 用于构建输出字符串。
遍历 sportsList 数组中的每个对象,解析时间并格式化。
如果赛事日期与输入日期匹配,将赛事信息添加到输出字符串中。
如果没有任何匹配的赛事,添加 "N/A" 以表示没有该日期的赛程。
StringBuilder output = new StringBuilder();
boolean flag = false;
for (int i = 0; i < list.size(); i++) {
JSONObject obj = list.getJSONObject(i);
String eventDateStr = obj.getString("time");
Date eventDate = null;
try {
eventDate = inputDateFormat.parse(eventDateStr);
} catch (ParseException e) {
System.err.println("Error parsing date: " + e.getMessage());
continue;
}
String formattedTime = outputDateFormat.format(eventDate);
String sport = obj.getString("sport");
String name = obj.getString("name");
String venue = obj.getString("venue");
String eventDateFormatted = new SimpleDateFormat("MMdd").format(eventDate);
if (eventDateFormatted.equals(date)) {
flag = true;
output.append("time: ").append(formattedTime).append("\n");
output.append("sport: ").append(sport).append("\n");
output.append("name: ").append(name).append("\n");
output.append("venue: ").append(venue).append("\n");
output.append("-----\n");
}
}
if (!flag) {
output.append("N/A").append("\n");
output.append("-----\n");
}
boolean flag = false;
这是用来控制当日期不在巴黎奥运会时间内的,当一个schedule指令被传入后,被分离的data日期会经过多个循环进行判断输出,当日期在我们所通过json文件转化为数组中时,flag
置true,表示这个日期是合法的,如果在循环体过后flag
依旧为false
,就可以知道日期非法并输出N/A
错误处理:
catch (IOException e) {
System.err.println("Error reading/writing file: " + e.getMessage());
} catch (Exception e) {
System.err.println("Error processing JSON: " + e.getMessage());
}
public class OlympicSearch {
public static void main(String[] args) throws IOException {
try {
String IN_TXT="src/data/"+args[0];//从要求的cmd命令格式可得到第一个参数为
String OUT_TXT="src/data/"+args[1];
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(IN_TXT), StandardCharsets.UTF_8));
String command = br.readLine();
while (command != null) {
System.out.println(command);
// read next line
Deal_Command.Deal_command(command,OUT_TXT);
if(command != null){
command = br.readLine();
}
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
定义核心接口
首先,定义一个核心接口来抽象出核心模块提供的功能。这有助于不同的实现类遵循相同的接口,从而便于在不同的场景中使用。
public interface OlympicCore {
void outputAllMedals(String outputPath) throws IOException;
void outputEventResults(String date, String outputPath) throws IOException;
}
}
创建一个类来实现这些接口。这个类将封装具体的业务逻辑,如读取文件、处理数据、输出结果等。
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
public class OlympicCoreImpl implements OlympicCore {
@Override
public void outputAllMedals(String outputPath) throws IOException {
String jsonStr = Read_File.readJsonFromFile("src/data/medals.json");
JSONObject root = JSONObject.parseObject(jsonStr);
JSONArray list = root.getJSONArray("medalsList");
List<JSONObject> medals = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
medals.add(list.getJSONObject(i));
}
medals.sort(Comparator.comparingInt(o -> o.getInteger("rank")));
StringBuilder output = new StringBuilder();
for (JSONObject obj : medals) {
String rank = obj.getString("rank");
int gold = obj.getInteger("gold");
int silver = obj.getInteger("silver");
int bronze = obj.getInteger("bronze");
int total = gold + silver + bronze;
output.append("rank: ").append(rank).append(": ").append(obj.getString("countryid")).append("\n");
output.append("gold: ").append(gold).append("\n");
output.append("silver: ").append(silver).append("\n");
output.append("bronze: ").append(bronze).append("\n");
output.append("total: ").append(total).append("\n");
output.append("-----\n");
}
Write_File.writeToFile(outputPath, output.toString());
}
@Override
public void outputEventResults(String date, String outputPath) throws IOException {
String jsonStr = Read_File.readJsonFromFile("src/data/sports.json");
JSONObject root = JSONObject.parseObject(jsonStr);
JSONArray list = root.getJSONArray("sportsList");
StringBuilder output = new StringBuilder();
SimpleDateFormat inputDateFormat = new SimpleDateFormat("M月d日HH:mm");
SimpleDateFormat outputDateFormat = new SimpleDateFormat("HH:mm");
boolean found = false;
for (int i = 0; i < list.size(); i++) {
JSONObject obj = list.getJSONObject(i);
String eventDateStr = obj.getString("time");
Date eventDate;
try {
eventDate = inputDateFormat.parse(eventDateStr);
} catch (ParseException e) {
System.err.println("Error parsing date: " + e.getMessage());
continue;
}
String formattedTime = outputDateFormat.format(eventDate);
String sport = obj.getString("sport");
String name = obj.getString("name");
String venue = obj.getString("venue");
String eventDateFormatted = new SimpleDateFormat("MMdd").format(eventDate);
if (eventDateFormatted.equals(date)) {
found = true;
output.append("time: ").append(formattedTime).append("\n");
output.append("sport: ").append(sport).append("\n");
output.append("name: ").append(name).append("\n");
output.append("venue: ").append(venue).append("\n");
output.append("-----\n");
}
}
if (!found) {
output.append("N/A").append("\n");
output.append("-----\n");
}
Write_File.writeToFile(outputPath, output.toString());
}
}
使用核心模块
命令行测试程序:
使用 OlympicCore
接口来处理命令。
public class OlympicSearch {
public static void main(String[] args) {
if (args.length != 2) {
System.err.println("Usage: java OlympicSearch <input_file> <output_file>");
return;
}
String IN_TXT = "src/data/" + args[0];
String OUT_TXT = "src/data/" + args[1];
OlympicCore core = new OlympicCoreImpl();
try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(IN_TXT), StandardCharsets.UTF_8))) {
String command;
while ((command = br.readLine()) != null) {
System.out.println(command);
if ("total".equalsIgnoreCase(command)) {
core.outputAllMedals(OUT_TXT);
} else if (command.startsWith("schedule")) {
String date = command.split(" ")[1];
core.outputEventResults(date, OUT_TXT);
} else {
System.out.println("Invalid command. Only 'total' and 'schedule MMDD' are supported.");
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
创建单元测试类来测试 OlympicCoreImpl 类的功能(这边测试结果和输入数据就直接贴图了)
import org.junit.jupiter.api.Test;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.*;
public class OlympicCoreImplTest {
@Test
public void testOutputAllMedals() throws IOException {
OlympicCore core = new OlympicCoreImpl();
core.outputAllMedals("test_output_medals.txt");
// Verify the content of the output file
// Add your verification code here
}
@Test
public void testOutputEventResults() throws IOException {
OlympicCore core = new OlympicCoreImpl();
core.outputEventResults("0811", "test_output_schedule.txt");
// Verify the content of the output file
// Add your verification code here
}
}
数据可视化
GUI 或数据可视化部分可以直接使用OlympicCore
接口中的方法,从而避免重复实现相同的逻辑。
public class VisualizationApp {
public static void main(String[] args) {
OlympicCore core = new OlympicCoreImpl();
}
}
PS:OlympicSearch.jar 用的还是对接口封装前的函数
总结:这个作业的完成由五个函数完成
逻辑顺序:
在这里对除了文件读入输出函数进行性能改进
Deal_cmmand
import java.io.IOException;
public class Deal_Command {
public static void Deal_command(String command, String OUT_TXT) throws IOException {
// 检查命令是否有效
if (command == null || command.trim().isEmpty()) {
handleInvalidCommand(OUT_TXT);
return;
}
String trimmedCommand = command.trim();
if ("total".equalsIgnoreCase(trimmedCommand)) {
Deal_Total.Total(trimmedCommand, OUT_TXT);
} else if (trimmedCommand.startsWith("schedule")) {
Deal_Schedule.Schedule(trimmedCommand, OUT_TXT);
} else {
handleInvalidCommand(OUT_TXT);
}
}
private static void handleInvalidCommand(String OUT_TXT) throws IOException {
StringBuilder output = new StringBuilder();
output.append("Error\n");
output.append("-----\n");
System.out.println("Error");
Write_File.writeToFile(OUT_TXT, output.toString());
}
}
使用 command.trim() 去掉命令前后的空白字符,确保即使命令包含额外的空格也能被正确处理。
提高字符串处理效率:优化了字符串处理Deal_Total
public class Deal_Total {
public static void Total(String command, String OUT_TXT) {
try {
// 从文件中读取 JSON 字符串
String jsonStr = Read_File.readJsonFromFile("src/data/medals.json");
JSONObject root = JSONObject.parseObject(jsonStr);
JSONArray list = root.getJSONArray("medalsList");
// 直接使用流操作提高性能和可读性
List<JSONObject> medals = list.toJavaList(JSONObject.class);
medals.sort(Comparator.comparingInt(o -> o.getIntValue("rank")));
StringBuilder output = new StringBuilder();
// 使用 StringBuilder 的链式调用来提高性能
medals.forEach(obj -> {
String rank = obj.getString("rank");
int gold = obj.getIntValue("gold");
int silver = obj.getIntValue("silver");
int bronze = obj.getIntValue("bronze");
int total = gold + silver + bronze;
output.append("rank: ").append(rank).append(": ").append(obj.getString("countryid")).append("\n")
.append("gold: ").append(gold).append("\n")
.append("silver: ").append(silver).append("\n")
.append("bronze: ").append(bronze).append("\n")
.append("total: ").append(total).append("\n")
.append("-----\n");
});
// 将结果写入文件
Write_File.writeToFile(OUT_TXT, output.toString());
} catch (IOException e) {
System.err.println("Error reading/writing file: " + e.getMessage());
} catch (Exception e) {
System.err.println("Error processing JSON: " + e.getMessage());
}
}
}
使用流操作提高性能:
使用 list.toJavaList(JSONObject.class)
代替传统的循环方式,将 JSONArray 转换为 List<JSONObject>
// 直接使用流操作提高性能和可读性
List<JSONObject> medals = list.toJavaList(JSONObject.class);
medals.sort(Comparator.comparingInt(o -> o.getIntValue("rank")));
StringBuilder output = new StringBuilder();
优化排序操作,使用 o.getIntValue("rank")
代替 o.getInteger("rank")
,提高性能。
在解析 JSON 时直接获取 JSONArray 并转换为 List<JSONObject>
,避免了重复的 getJSONObject(i)
操作,这样可以减少在内存中的操作开销。
// 使用 StringBuilder 的链式调用来提高性能
medals.forEach(obj -> {
String rank = obj.getString("rank");
int gold = obj.getIntValue("gold");
int silver = obj.getIntValue("silver");
int bronze = obj.getIntValue("bronze");
int total = gold + silver + bronze;
});
``Deal_Schedule``
将日期格式化的 SimpleDateFormat 对象声明为静态常量。这可以减少对象的重复创建,提高效率,并保证格式一致性。
```javascript
public class Deal_Schedule {
private static final SimpleDateFormat INPUT_DATE_FORMAT = new SimpleDateFormat("M月d日HH:mm");
private static final SimpleDateFormat OUTPUT_DATE_FORMAT = new SimpleDateFormat("HH:mm");
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MMdd");
将 flag 的初始化放在循环外部并在匹配时进行更新,避免了不必要的变量赋值。:
简化异常处理:
将异常处理放在适当的位置,减少了多余的 catch 代码,并确保能捕获并处理所有异常情况。
catch (IOException e) {
System.err.println("Error reading/writing file: " + e.getMessage());
} catch (Exception e) {
System.err.println("Error processing JSON: " + e.getMessage());
}
太难了,之前都没有这么具体地使用idea,而且完成像单元测试和性能优化这种改动,json文件的爬取也是向别的同学请教的,基本上感觉是从零开始边学边做,学到了挺多东西,接口有想法去实现但感觉能力不够QAQ