122
社区成员
发帖
与我相关
我的任务
分享| 这个作业属于哪个课程 | <2302软件工程> |
|---|---|
| 这个作业要求在哪里 | <软件工程第二次作业--文件读取> |
| 这个作业的目标 | <完成对**世界游泳锦标赛跳水项目**相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序> |
| 其他参考文献 | 《码出高效_阿里巴巴Java开发手册》、单元测试和回归测试、源代码管理、[IDEA单元测试](IDEA单元测试--详细使用步骤_idea 单元测试-CSDN博客) |
[meiyuan0369 / Project Java](meiyuan0369 / Project Java · GitCode)
分钟计算:天数*分钟数
| PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 1*120 | 5*30 |
| • Estimate | • 估计这个任务需要多少时间 | 5*240 | 8*240 |
| Development | 开发 | 3*240 | 5*240 |
| • Analysis | • 需求分析 (包括学习新技术) | 1*240 | 1*240 |
| • Design Spec | • 生成设计文档 | 1*240 | 1*240 |
| • Design Review | • 设计复审 | 1*60 | 3*30 |
| • Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 1*30 | 1*30 |
| • Design | • 具体设计 | 4*30 | 5*30 |
| • Coding | • 具体编码 | 3*180 | 5*180 |
| • Code Review | • 代码复审 | 1*60 | 1*60 |
| • Test | • 测试(自我测试,修改代码,提交修改) | 1*120 | 2*60 |
| Reporting | 报告 | 1*120 | 1*240 |
| • Test Repor | • 测试报告 | 1*30 | 1*60 |
| • Size Measurement | • 计算工作量 | 1*30 | 1*30 |
| • Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 1*30 | 1*30 |
| 合计 | 3660 | 5100 |





[ ... ]结构的数组,里面由多个{ ... }对象组成,每个对象有个key值:"CountryName",对应的value:"Austria" 就是国家名称。这里的对象在数组中没有按字典排序,后续需要进行处理。
"Participations",对应的value又是一个数组,数组里面每个对象都包含"Gender"、"PreferredLastName"和"PreferredFirstName"三个属性,正是我们需要的。在同一个国家中的每个运动员,已经按选手的名(Last Name)为次要关键字升序排序了,后续无需处理。
players命令后,调用SearchHelper.java接口中的searchAthletes方法。使用Java中第三方库org.json解析Json文件。TreeMap对整个国家的所有运动员为一个整体进行排序,key为"CountryName",value为某个国家运动员信息的对象。TreeMap排序后的键值对依次进行处理,对每个key为"CountryName"的JSONObject,用getJSONArray方法解析Json中key为"Participations"的数组getInt方法或者getString方法得到对应的性别"Gender"、姓名"PreferredLastName"和"PreferredFirstName",按要求格式保存到BufferedWriter,最后输入到output文件中。使用TreeMap对输出内容进行排序,利用现有容器,代码简洁明了。
同2.1.1,在比赛结果中打开开发者工具。
同2.1.2、2.1.3,点击“RESULTS”中的某项目发起网络请求,找到我们需要的比赛结果的Json文件并保存下来。

"Heats",其对应的value是一个数组,数组中的对象表示决赛、半决赛和预赛的成绩,由key为"Name"区分。
"Final"所在的对象中,查看key为"Results"对应的数组,数组中的每个对象就是根据排名"Rank"排序的比赛结果,总得分key值"TotalPoints"对应的value为"248.95",排名key值"Rank"对应的value为1。详细得分在key值"Dives"对应的数组中得到,数组的每个对象就是不同评委的打分,例如第一个评委打分key值"DivePoints"对应的value为"51.60",其余同理可以在组数的对象中得到。
result命令的对象,并且一个比赛项目对应一个Json文件,如果为每个比赛项目都专门写一个方法,而又每个比赛项目的结果Json文件结构一致,将会有大量重复的代码。所以需要用一个方法来处理不同的10个项目的文件。"Rank"排序了,所以本题只需要按顺序输出就好,无需排序。本题的难点在于,每个评委的打分在Json中嵌套得比较深,所以只需要细心编写代码,正确解析Object和Array,就没有什么大问题。BufferedWriter,最后输入到output文件中。实现动态文件名,根据输入指令,转化成对应将要访问的文件名。
根据需求,我们需要实现两个功能:打印全部运动员和打印全部比赛结果。为此,我们考虑使用 ==Java 8== 引入的新特性,即在接口中使用静态方法。这样一来,继承者可以直接调用接口中的静态方法。
searchAthletes 的静态方法,内部使用 org.json 库解析 JSON 数据,并根据题目要求进行打印。searchResult 的静态方法,根据传入的项目名称,打开对应的 JSON 文件,同样使用 org.json 库解析 JSON 数据,并按照题目要求进行打印。writeTxt 的静态方法。输出内容和指定输出文件都由方法参数传入。org.json 的 JAR 包。然后在接口代码中导入 org.json.JSONArray 和 org.json.JSONObject。这样我们就可以调用 org.json 的 JSONArray 和 JSONObject 类去解析相应的数据。DWASearch 中,我们实现了 implements SearchHelper,这样我们就可以在任意地方通过调用接口 SearchHelper.searchAthletes(outputFile)、SearchHelper.searchResult(event, outputFile) 和 SearchHelper.writeTxt("someThing", outputFile) 得到相应的处理方法。判断命令输入的正确性和处理
while ((line = br.readLine()) != null) {
command = Arrays.asList(line.split(" "));//把line的内容按单词放到commond中
if (command.get(0).equals("players")) {
SearchHelper.searchAthletes(outputFile);
} else if (command.get(0).equals("result")) {
if(command.size() < 4) {
SearchHelper.writeTxt("N/A\n-----\n",outputFile);
continue;
}
String event = command.get(1) + " " + command.get(2) + " " + command.get(3);
if (events.contains(event) && command.size() == 4) {
SearchHelper.searchResult(event,outputFile);
} else if (events.contains(event) && command.get(4).equals("detail") && command.size() == 5) {
SearchHelper.searchDetail(event,outputFile);
} else {
SearchHelper.writeTxt("N/A\n-----\n", outputFile);
}
} else {
SearchHelper.writeTxt("Error\n-----\n", outputFile);
}
}
org.json的使用
while ((line = reader.readLine()) != null) {
jsonContent.append(line);
}
JSONArray athletesArray = new JSONArray(jsonContent.toString());
for (int i = 0; i < athletesArray.length(); i++) {
JSONObject Country = athletesArray.getJSONObject(i);
String countryName = Country.getString("CountryName");
countryList.put(countryName, Country);
}
打开动态文件名
String event_ = event.replace(" ", "_");
String inputFilePath = "222100129/datas/results/" + event_ + ".json";// 构建结果文件的路径
try (BufferedReader reader = new BufferedReader(new FileReader(inputFilePath));
BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile, true));
)
{ ... }
在处理输入指令时,可能会出现重复的情况。如果每次重复指令都执行相同的文件读写操作,将导致大量时间的浪费。考虑到计算机存储成本较低于时间成本,我们可以采用空间换时间的思路,即使用 HashMap 存储键值对作为缓存机制的实现体。
在这个实现中,我们将指令作为键(key),相应的输出文件内容作为值(value)。这样,每次接收到指令时,首先检查 HashMap 中是否存在对应的指令。如果存在,则直接从 HashMap 中获取相应的内容并输出到输出文件中。如果没有找到,则执行正常的文件读写操作,并将结果保存到 HashMap 中。
通过这种方式,从第二次执行相同指令开始,就可以减少代码运行所需的计算时间。由于指令数量是有限的,因此不会占用大量内存。
这种优化思路充分利用了 HashMap 的快速查找特性,避免了重复读取相同指令时的不必要的文件读写操作,从而提高了程序的执行效率。
HashMap<String, String> cache = new HashMap<>();
...
if (cache.containsKey("players")) {
writeTxt(cache.get("players"), outputFile);
return;
}
...
writer.write(output.toString());
cache.put("players", output.toString());
对searchAthletes方法执行两次,分别观察两次运行的耗时。
测试代码如下:
@Test
public void searchAthletes() {
long startTime=System.currentTimeMillis(); //获取开始时间
SearchHelper.searchAthletes("output.txt");
long endTime1=System.currentTimeMillis();
long time1 = endTime1-startTime;
SearchHelper.searchAthletes("output.txt");
long endTime2=System.currentTimeMillis();
long time2 = endTime2-endTime1;
System.out.println("time1: "+time1+"ms");
System.out.println("time2: "+time2+"ms");
}
运行结果如下:

在处理输入流时,我们发现使用
FileWriter进行写操作速度较慢。这可能是因为FileWriter每次写入数据时都会立即将数据刷新到磁盘,而这种操作对于大量数据的写入来说可能效率较低。
BufferedWriter 类来包装 FileWriter,从而对输出流进行更加精细的控制。BufferedWriter 会在内部维护一个缓冲区,在写入数据时先将数据存储在缓冲区中,当缓冲区满了或者手动调用 flush() 方法时,再将数据一次性刷新到磁盘上,这样可以减少频繁的磁盘IO操作,提高写操作的效率。BufferedWriter,我们可以有效地减少磁盘IO次数,从而提高了写操作的速度和效率,尤其是在处理大量数据时表现更加突出。try (BufferedReader reader = new BufferedReader(new FileReader("222100129/datas/athletes.json"));
BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile, true))){
...
}
以下是改进前的代码:
StringBuilder jsonContent = new StringBuilder();
int character;
while ((character = reader.read()) != -1) {
jsonContent.append((char) character);
}
JSONArray athletesArray = new JSONArray(jsonContent.toString());
以下是改进前的执行时间:

以下是改进后的执行时间:

在Java开发中,为了确保代码的质量和可靠性,我们经常需要进行单元测试。JUnit是Java领域最流行的单元测试框架之一,它提供了一种简单而强大的方式来编写和运行测试用例。
通过JUnit,我们可以轻松地对代码的各个部分进行测试,包括函数、方法、类等。这样一来,我们可以在开发过程中不断验证代码的正确性,减少BUG的产生,并提高代码的可维护性和可读性。
DWASearch类测试测试代码:
public class DWASearchTest {
@Test
public void test1(){
//错误的参数输入数量
DWASearch.main(new String[]{"input.txt"});
}
@Test
public void test1_1(){
//错误的参数输入数量和文件后缀
DWASearch.main(new String[]{"input"});
}
@Test
public void test2(){
//正确的参数个数输入
DWASearch.main(new String[]{"input.txt","output.txt"});
}
@Test
public void test2_1(){
//正确的参数个数输入
DWASearch.main(new String[]{"input.txt","putout.txt"});
}
@Test
public void test2_2(){
//错误的参数输入文件后缀
DWASearch.main(new String[]{"input.txt","putout"});
}
@Test
public void test2_3(){
//错误的参数输入文件后缀
DWASearch.main(new String[]{"input.txt","putout.p"});
}
@Test
public void test3(){
//不正确的参数输入数量
DWASearch.main(new String[]{"input.txt","output.txt","redundant.txt"});
}
@Test
public void test3_1(){
//不正确的参数输入数量和文件后缀
DWASearch.main(new String[]{"input.txt","output","redundant.txt"});
}
}

结果分析:因为DWASearch类中没有嵌套类,所以类的覆盖率100%;且DWASearch类中只有一个主函数Main,所以方法覆盖率也是100%;但因为参数个数错误,抛出异常,提前终止程序,所以代码行的覆盖率为23%。







以上所有结果符合预期
测试代码:
@Test
public void searchAthletesTest(){
//不正确的参数输入数量和文件后缀
DWASearch.main(new String[]{"input.txt","output.txt"});
}
input.txt内容:

output.txt内容:

所有错误指令能够正确识别,符合预期
测试代码:
@Test
public void testCoverage(){
//正确的参数个数输入
DWASearch.main(new String[]{"input.txt","output.txt"});
DWASearch.main(new String[]{"input.txt"});
DWASearch.main(new String[]{"nonexistence.txt","output.txt"});
}
input.txt内容:

测试覆盖率结果:

类和方法的覆盖率都达到100%了,只有代码行的覆盖率达不到100%。返回项目源代码页面,查看代码行数标号旁边的颜色,如果是绿色,表示刚才测试执行了这行代码。我们找红色的代码行:

上图说明我们在searchAthletes方法中没有执行第二次players指令。现在我把players放到input.txt文件末尾,让程序执行第二次players指令。如下图所示input.txt内容:

再次执行测试代码,运行结果如下:

由上图可知,代码行覆盖率由96%提升到97%,并且行数标号旁边的颜色变成绿色:

其余未执行的代码行均为打开output.txt文件写数据,如果不发生磁盘已满、文件被其他程序锁定等情况,是无法执行catch代码块中的代码:





同4.1 判断命令输入的正确性和处理
try (BufferedReader br = new BufferedReader(new FileReader(args[0]))) {
...
} catch (IOException e) {
e.printStackTrace();
}
首先,我深刻体会到了使用不熟悉的爬虫、Git、GitCode、Maven等相关技术来完成项目的挑战。虽然我之前有一些接触,但对这些技能的实际运用了解不够深入,因此在项目中感到了“书到用时方恨少”的情况。
其次,我对软件工程的流程有了更深刻的理解。从需求分析、数据查找、项目设计、代码编写到项目测试,我亲身经历了整个流程。特别是项目测试阶段,我开拓了新的视野。我以前以为测试只是简单地运行代码并输入各种数据,但现在我学会了使用Junit进行测试,提高了测试的效率,还能通过覆盖率来完善测试。
最后,我深刻认识到单纯编写代码并不困难,真正的挑战在于提高程序的性能和容错性。这需要不断学习他人优秀的代码,丰富自己的经验。另外,我发现环境配置对项目时间的影响很大。我经常需要在网络上查找文章,甚至向同学请教,才能解决编译器的某些设置问题。但我相信practice makes perfect,发现自己的不足并不断完善学习,才能在下次遇到类似问题时更快地解决。