Bootstrap

软件工程实践第二次作业——文件读取

这个作业属于哪个课程21级软件工程
这个作业要求在哪里软件工程第二次作业–文件读取
这个作业的目标完成对世界游泳锦标赛跳水项目相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序
其他参考文献工程师的能力评估与发展源代码管理单元测试与回归测试

目录

1.GitCode仓库

项目地址

2.PSP表格

PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划3020
• Estimate• 估计这个任务需要多少时间3020
Development开发10501195
• Analysis• 需求分析 (包括学习新技术)9080
• Design Spec• 生成设计文档3040
• Design Review• 设计复审3020
• Coding Standard• 代码规范 (为目前的开发制定合适的规范)2010
• Design• 具体设计3025
• Coding• 具体编码700800
• Code Review• 代码复审3020
• Test• 测试(自我测试,修改代码,提交修改)120200
Reporting报告10090
• Test Repor• 测试报告3060
• Size Measurement• 计算工作量2010
• Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划5020
合计11851305

3.解题思路描述

3.1 Json数据分析

首先来分析一下获取到的json数据,包括三个部分。
请添加图片描述
第一部分是results包,包括了各个项目的决赛结果。其中,Heats数组中有两个元素,分别是决赛和预赛,而我们只需要决赛结果即可。决赛结果数组是三层嵌套数组,最外层根据"TotalPoints"划分,次外层根据"Dives"划分,最内层根据"JudgesScores"划分。分析数据发现,FullName、Rank、TotalPoints和DivePoints这几个数据是我们需要的。
请添加图片描述
请添加图片描述

第二部分是athletes.json,其中的数据是个双层数组,外层数组根据国家区分,内层数组根据运动员区分。分析数据发现,CountryName、PreferredLastName、PreferredFirstName、DisciplineName和Gender这几个数据是我们需要的。
请添加图片描述

第三部分是event.json,包括了比赛相关信息。

3.2 Json数据处理

我使用alibaba的fastjson2来处理json数据。
根据json数据的关键词来获取相关数据。

JSONObject resultObject = resultsArray.getJSONObject(i);
//根据json文件中的key值获取对应的value(有的value要处理,有的value直接获取即可)
String country = resultObject.getString("CountryName");
//获取每个国家的运动员信息
JSONArray athletesArray = resultObject.getJSONArray("Participations");
//遍历内层运动员数组
for (int j = 0; j < athletesArray.size(); j++) {
  String fullname = athletesArray.getJSONObject(j).getString("PreferredLastName") + " " + athletesArray.getJSONObject(j).getString("PreferredFirstName");
  fileWriter.write(("Full Name:" + fullname + "\n").getBytes());
  String gender = (athletesArray.getJSONObject(j).getInteger("Gender") == 0 ? "Male" : "Female");
  fileWriter.write(("Gender:" + gender + "\n").getBytes());
  fileWriter.write(("Country:" + country + "\n").getBytes());
  fileWriter.write(("-----" + "\n").getBytes());
}

3.3 传入文件地址,循环读取操作指令

这一功能调用inputTest方法来实现,inputTest方法通过逐行读取输入文件中的操作指令,并根据指令的不同调用相应的方法进行处理,最终将处理结果输出到指定的输出文件中。具体实现思路如下:

  • 根据传入的输入文件路径,使用 BufferedReader 逐行读取输入文件中的操作指令,并进行处理。
  • 对于每一行读取到的操作指令,首先使用 trim() 方法去除首尾空白字符,确保操作指令的格式正确。
  • 如果操作指令是 “players”,则调用 outputAllAthletesInfo 方法输出所有选手信息。
  • 如果操作指令以 "result " 开头,则解析指令中的比赛项目信息,判断其是否合法。
  • 如果比赛项目合法,则根据比赛项目信息调用 outputEventResult 方法输出比赛结果。
  • 如果操作指令不符合以上两种格式,则输出 “Error” 到输出文件,并在其后输出 “-----”,以表示错误信息结束。
  • 循环处理完所有操作指令后,关闭输入文件的读取流,处理结束。

3.4 功能1:输出所有选手信息

这一功能调用outputAllAthletesInfo方法来实现,outputAllAthletesInfo方法根据缓存是否存在,选择从文件中读取或者直接使用缓存中的运动员信息,并将这些信息逐个写入输出文件中。具体实现思路如下:

  • 方法会检查是否已经存在运动员信息的缓存(即 athletes 数组),如果缓存为空,则说明是首次输出所有选手信息,需要从文件中读取。
  • 如果缓存不为空,则直接使用缓存中的运动员信息进行输出。
  • 对于首次输出选手信息的情况,首先从 athletes.json 文件中读取数据。这个文件中的数据是一个双层数组,外层数组按国家区分,内层数组按运动员区分。
  • 遍历外层国家数组,获取每个国家的名称和对应的运动员信息数组。
  • 对于每个国家的运动员信息数组,遍历其中的每个运动员,获取其全名、性别和所属国家信息。
  • 将每个运动员的信息写入输出文件中,包括全名、性别和所属国家,并在每个运动员信息之后输出 “-----” 表示信息结束。
  • 如果缓存不为空,则直接使用缓存中的运动员信息进行输出,遍历缓存中的 athletes 数组,将每个运动员的信息写入输出文件,并在每个运动员信息之后输出 “-----” 表示信息结束。

3.5 功能2:输出决赛每个运动项目结果

这一功能调用outputEventResult方法来实现,outputEventResult方法根据缓存是否存在指定比赛项目的结果,选择从文件中读取或者直接使用缓存中的结果,并将这些结果逐个写入输出文件中。具体实现思路如下:

  • 方法会检查缓存中是否已经存在指定比赛项目的结果,如果存在,则直接从缓存中获取结果并输出。
  • 如果缓存中不存在指定比赛项目的结果,则说明是首次输出该比赛项目的结果,需要从文件中读取。
  • 根据传入的比赛项目名称构建相应的 JSON 文件路径,从文件中读取该比赛项目的数据。
  • 解析 JSON 数据,获取比赛项目的结果数组,这个数组按照不同的标准进行层级划分。
  • 遍历比赛项目的结果数组,获取每个运动员的全名、排名、总分以及每次裁判打分的信息。
  • 将每个运动员的信息写入输出文件中,包括全名、排名、总分和裁判打分信息,并在每个运动员信息之后输出 “-----” 表示信息结束。
  • 将结果缓存起来,以便下次快速获取。
  • 如果缓存中已经存在相同比赛项目的结果,则直接从缓存中获取结果并输出,避免重复读取文件和处理数据。

4.设计与实现过程

4.1 类设计

  • 实体类:设计了Athlete和Result类来存放运动员和决赛结果的相关数据。
  • Lib类:包括主要的逻辑处理函数,是项目的核心类。
  • DWASearch类:包含main函数,是项目的入口。
  • LibTest类:测试类,用于Lib类的单元测试。

4.2 函数设计

  • 主函数:传入参数并检测正确性
  • 逻辑函数:
    • inputTest:通过逐行读取输入文件中的操作指令,并根据指令的不同调用相应的方法进行处理,最终将处理结果输出到指定的输出文件中。
    • outputAllAthletesInfo:根据缓存是否存在,选择从文件中读取或者直接使用缓存中的运动员信息,并将这些信息逐个写入输出文件中。
    • outputEventResult:根据缓存是否存在指定比赛项目的结果,选择从文件中读取或者直接使用缓存中的结果,并将这些结果逐个写入输出文件中。
  • 工具函数:
    • isValidEvent:用于检查比赛项目名称是否合法。
    • getStrFromData:用于将指定的 JSON 文件读取并转换为字符串返回。
//传入读入和读出文件地址,循环读取操作指令(判断是否是正确指令)
public static void inputTest(String input, String output);

//检查比赛项目名称是否合法
private static boolean isValidEvent(String gender, String distance, String discipline);

//将指定的json文件读取出来,并转为字符串返回
private static String getStrFromData(String jsonFileAddress);

//输出所有选手信息
public static void outputAllAthletesInfo(FileOutputStream fileWriter);

//输出决赛每个运动项目结果
public static void outputEventResult(String jsonName, FileOutputStream fileWriter);

5.关键代码展示

5.1 inputTest方法

 public static void inputTest(String input, String output) {
        try {
            String inputPath = basedir  + input;
            String outputPath = basedir + output;
            FileOutputStream fileOutputStream = new FileOutputStream(outputPath, true);
            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(inputPath), StandardCharsets.UTF_8));
            String command;
            while ((command = br.readLine()) != null) {
                command = command.trim();
                if (command.equals("players")) {
                    outputAllAthletesInfo(fileOutputStream);
                } else if (command.startsWith("result ")) {
                    String[] parts = command.split(" ");
                    if (parts.length != 4 || !isValidEvent(parts[1], parts[2], parts[3])) {
                        fileOutputStream.write(("N/A" + "\n").getBytes());
                        fileOutputStream.write(("-----" + "\n").getBytes());
                    } else {
                        // 操作指令合法,调用输出比赛结果的方法
                        String jsonName = parts[1] + " " + parts[2] + " " + parts[3] + ".json";
                        outputEventResult(jsonName, fileOutputStream);
                    }
                } else {
                    fileOutputStream.write(("Error" + "\n").getBytes());
                    fileOutputStream.write(("-----" + "\n").getBytes());
                }
            }
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

5.2 outputEventResult方法

public static void outputEventResult(String jsonName, FileOutputStream fileWriter) {
        try {
            //若并非第一次输出一个运动项目结果(之前已输出过同一个),则可以直接输出缓存中的结果
            if (results != null && results.containsKey(jsonName)) {
                List<Result> resultList = results.get(jsonName);
                for (Result result : resultList) {
                    fileWriter.write(("Full Name:" + result.getFullName() + "\n").getBytes());
                    fileWriter.write(("Rank:" + result.getRank() + "\n").getBytes());
                    fileWriter.write(("Score:" + result.getScore() + "\n").getBytes());
                    fileWriter.write(("-----" + "\n").getBytes());
                }
            } else {
                //获取比赛的全部数据
                JSONObject jsonObject = JSONObject.parseObject(getStrFromData(basedir + "data/results/" + jsonName));
                //获取第一层Heats数组
                JSONArray heatsArray = jsonObject.getJSONArray("Heats");
                //Heats数组中有两个元素,分别是决赛和预赛
                JSONObject finalObject = heatsArray.getJSONObject(0);
                //获取决赛中的结果,决赛结果数组是三层嵌套数组,最外层根据"TotalPoints"划分,次外层根据"Dives"划分,最内层根据"JudgesScores"划分
                JSONArray resultsArray = finalObject.getJSONArray("Results");
                //存储运动项目结果的列表
                List<Result> resultList = new ArrayList<>();
                //获取每个选手的得分信息
                for (int i = 0; i < resultsArray.size(); i++) {
                    JSONObject athleteObject = resultsArray.getJSONObject(i);
                    String fullName = athleteObject.getString("FullName");
                    String rank = athleteObject.getString("Rank");
                    String totalPoints = athleteObject.getString("TotalPoints");
                    //总得分中每个裁判的打分
                    String judgesScores = "";
                    JSONArray divesArray = athleteObject.getJSONArray("Dives");
                    for (int j = 0; j < divesArray.size(); j++) {
                        JSONObject divesScore = divesArray.getJSONObject(j);
                        String onceScore = divesScore.getString("DivePoints");
                        //生成裁判打分的连加字符串
                        if (j != divesArray.size() - 1) {
                            judgesScores += onceScore + "+";
                        } else {
                            judgesScores += onceScore;
                        }
                    }
                    //生成最终的得分字符串(例如:46.00 + 42.90 + 50.70 + 54.00 + 46.80 = 240.40)
                    String finalScore = judgesScores + "=" + totalPoints;
                    //将运动项目的结果缓存入HashMap,方便下次快速获取
                    Result result = new Result(fullName, rank, finalScore);
                    resultList.add(result);
                    //输出决赛的项目结果
                    fileWriter.write(("Full Name:" + fullName + "\n").getBytes());
                    fileWriter.write(("Rank:" + rank + "\n").getBytes());
                    fileWriter.write(("Score:" + finalScore + "\n").getBytes());
                    fileWriter.write(("-----" + "\n").getBytes());
                }
                results.put(jsonName, resultList);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

5.3 文件路径处理(解决jar包无法访问txt文件的问题)

public static String basedir;

static {
  basedir = Paths.get("").toAbsolutePath().toString();
  basedir = basedir.substring(0, basedir.indexOf("project-java") + 12);
  basedir = basedir + "\\112101225\\src\\";
}

6.程序性能改进

这个程序的主要功能就是json数据的读取、处理和输出,那么主要影响程序性能的因素就是json数据的反复读取。即在短期内反复读取同一份json数据时,可以考虑使用缓存,把数据先存下来,然后短期内反复读取时,可以之间从缓存中获取所需数据。

// 运动员信息缓存数组
public static Athlete[] athletes;

// 比赛结果缓存HashMap
public static HashMap<String, List<Result>> results = new HashMap<>();

if (athletes == null) {
  //初次输出,进行缓存处理
}else{
  //多次输出,直接从缓存中获取数据
}

if (results != null && results.containsKey(jsonName)) {
  //初次输出,进行缓存处理
}else{
  //多次输出,直接从缓存中获取数据
}

7.单元测试展示

单元测试主要是对程序中的inputTest、outputAllAthletesInfo和outputEventResult方法进行测试,编写测试代码如下:

public static final String INPUT_FILE = "input.txt";
    public static final String OUTPUT_FILE = "output.txt";
    Lib lib = new Lib();

    @Test
    public void DWASearchTest() {
        DWASearch.main(new String[]{INPUT_FILE, OUTPUT_FILE});
        System.out.println("DWASearch方法测试完成");
    }

    @Test
    public void inputTest() {
        lib.inputTest(INPUT_FILE, OUTPUT_FILE);
        System.out.println("inputTest方法测试完成");
    }

    @Test
    public void outputAllAthletesInfo() throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream(OUTPUT_FILE, true);
        lib.outputAllAthletesInfo(fileOutputStream);
        System.out.println("outputAllAthletesInfo方法测试完成");
    }

    @Test
    public void outputEventResult() throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream(OUTPUT_FILE, true);
        String jsonName = "women 1m springboard.json";
        lib.outputEventResult(jsonName, fileOutputStream);
        System.out.println("outputEventResult方法测试完成");
    }

处理结果如下:
请添加图片描述
请添加图片描述

8.异常处理说明

8.1 主函数参数校验

//判断传入的参数是否是两个
if (args.length != 2) {
  System.out.println("需要同时传入输入文件和输出文件的路径");
  return;
}

8.2 IO异常处理

使用try-catch语句块来进行异常处理,若有异常会抛出。

 try {
    //代码细节
} catch (IOException e) {
    e.printStackTrace();
}

9.心路历程与收获

一开始看到作业需求,我并没有很好的思路,所以我反复阅读了很多遍作业需求,思考每个细节“是什么、为什么、怎么做”,逐渐对我所要写的程序有了个大致的概念。在实现过程中,我采用模块化的思路,将一个大问题拆解为一个个小问题,再逐一突破实现它们。不可避免的,我遇到了很多不懂的新技术和奇怪的bug,这也大大地锻炼了我的资料检索能力和debug调试能力。总体而言,我认为这次的作业我有很大的收获。

;