软件工程第二次作业--文件读取

222100105黄娇茹 2024-03-02 18:22:10
这个作业属于哪个课程2302软件工程
这个作业要求在哪里软件工程第二次作业--文件读取
这个作业的目标命令行控制输出2024世界游泳锦标赛跳水项目的选手信息和比赛结果
其他参考文献Json详解及fastjson教程IDEA单元测试

目录

  • Gitcode项目地址
  • PSP表格
  • 解题思路描述
  • 问题 1.获取和解析JSON文件
  • 问题 2.JSON数据解析
  • 问题 3.读写文件
  • 接口设计和实现过程
  • 1. 接口设计
  • 2. 输出运动员信息
  • 3. 输出比赛结果
  • 4. 独到之处
  • 关键代码展示
  • 性能改进
  • 引入缓存
  • 优化结果
  • 单元测试
  • 异常处理
  • 心得体会

Gitcode项目地址

Gitcode地址

PSP表格

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

解题思路描述

问题 1.获取和解析JSON文件

  1. 在待爬取的网页内按F12,点“网络”,选中所需的JSON文件,ctrl+s保存

    获取比赛结果相关文件


    获取运动员信息相关文件

  2. 了解JSON文件结构,以便后续的读取
    athletes.json中:

    运动员信息解析


    events.json中:

    在这里插入图片描述


    根据对应ID,找到108c795d-5e4f-4dc6-acea-0bc70bfd1928.json中:

    在这里插入图片描述


    在这里插入图片描述

    问题 2.JSON数据解析

  3. 在IDEA中导入fastjson2的jar包
    下载地址
    导包教程
  4. 利用fastjson2的JSONArray和JSONObject层层解析文件,获取所需的数据【 注:JSONArray解析[],JSONObject解析{} 】

问题 3.读写文件

  1. 本次作业需要读入input文件中的指令,并将相关的比赛信息写入output文件里。通过查阅资料,我选用BufferedReader实现input和JSON文件的读取,用BufferedWriter实现output文件的写入。
  2. 由于该程序需要频繁地改动待输出字符串,因此我使用性能较好的StringBuilder。将待输出的内容存在StringBuilder中,每读1000条指令,将其写入输出文件,写入后清空,再存入内容,直到所有指令读取完毕。

接口设计和实现过程

1. 接口设计

Lib:

  • outputAthletesInfo():获取选手信息
  • outputEventsResult():获取比赛结果
  • getResultId():通过比赛项目名获取对应文件ID
  • getJsonInfo():获取JSON文件的内容

DWASearch:

  • main():读取、分析指令,缓存,写入文件

2. 输出运动员信息

  • DWASearch中的HashMap<String, String> maps,里面存有之前解析过的有效指令与其对应的比赛信息。 main()函数识别“players”指令后,先查看缓存中是否有现成数据,若有则输出,若无则调用Lib的outputAthletesInfo()获取相应内容。
  • 在Lib中,该方法先调用getJsonInfo()获取JSON文件内容,然后利用fastjson2的JSONArray和JSONObject解析数据,最后返回运动员信息。经DWASearch的main()写入到输出文件。

    在这里插入图片描述

3. 输出比赛结果

  • 缓存部分参照上一点,若无缓存,调用Lib的outputEventsResult()
  • 在Lib中,该方法调用getResultId()通过比赛名获取对应的文件ID,调用getJsonInfo()获取文件内容,层层解析,获得比赛结果,返回给DWASearch的main(),最后写入到输出文件。

    在这里插入图片描述

4. 独到之处

  • 封装了两大功能,项目结构清晰,易于维护
  • 待输出的数据存在StringBuilder,性能更优
  • 每读1000条指令,输出StringBuilder一次,减少写文件的次数,运行速度更快

    关键代码展示

  1. 分析、处理指令

    StringBuilder info = new StringBuilder();//存储输出内容
    
             //处理合集中的单条指令
             for (int i = 0; i < commands.size(); i++) {
                 //若有缓存,则取出对应内容
                 if (maps.containsKey(commands.get(i))) {
                     info.append(maps.get(commands.get(i)));
    
                     //每读1000条写入一次
                     if ((i + 1) % 1000 == 0) {
                         BufferedWriter bw = new BufferedWriter(new FileWriter(outputFile, true));
                         bw.write(info.toString());
                         bw.close();
                         info.setLength(0);
                     }
                     continue;
                 }
    
                 if (commands.get(i).equals("players")) {//输出所有选手信息
                     //缓存指令和对应的输出内容
                     maps.put("players", Lib.outputAthletesInfo());
                     info.append(Lib.outputAthletesInfo());
                 } else if (commands.get(i).indexOf(" ") != -1) {
                     String firstStr = commands.get(i).substring(0, commands.get(i).indexOf(" "));
                     if (!firstStr.equals("result")) {//无法识别的指令
                         info.append("Error\n-----\n");
                     } else {
                         String eventName = commands.get(i).substring(commands.get(i).indexOf(" ") + 1);
                         if (list.contains(eventName)) {//输出比赛结果
                             //缓存指令和对应的输出内容
                             maps.put(commands.get(i), Lib.outputEventsResult(eventName));
                             info.append(Lib.outputEventsResult(eventName));
                         } else {//比赛项目名称不正确
                             info.append("N/A\n-----\n");
                         }
                     }
                 } else {//无法识别的命令
                     info.append("Error\n-----\n");
                 }
    
                 //每读1000条写入一次
                 if ((i + 1) % 1000 == 0) {
                     BufferedWriter bw = new BufferedWriter(new FileWriter(outputFile, true));
                     bw.write(info.toString());
                     bw.close();
                     info.setLength(0);
                 }
             }
    
             //写入剩余内容
             BufferedWriter bw = new BufferedWriter(new FileWriter(outputFile, true));
             bw.write(info.toString());
             bw.close();
             info.setLength(0);
    
  2. 获取运动员信息

    //输出所有选手信息
     public static String outputAthletesInfo() throws IOException {
         //存储待输出的选手信息
         StringBuilder athletesInfo = new StringBuilder();
         JSONArray jsonArray = JSONArray.parseArray(getJsonInfo(ATHLETES_FILE));
         for (int i = 0; i < jsonArray.size(); i++) {
             //获取一个国家所有运动员的信息
             JSONObject jsonObject = jsonArray.getJSONObject(i);
             //获取国名
             String countryName = jsonObject.getString("CountryName");
             JSONArray athleteList = jsonObject.getJSONArray("Participations");
             for (int j = 0; j < athleteList.size(); j++) {
                 //获取该国家中一位运动员的信息
                 JSONObject athlete = athleteList.getJSONObject(j);
                 athletesInfo.append("Full Name:" + athlete.getString("PreferredLastName") + " " + athlete.getString(
                         "PreferredFirstName") + "\n");
                 athletesInfo.append("Gender:" + ("0".equals(athlete.getString("Gender")) ? "Male" : "Female") + "\n");
                 athletesInfo.append("Country:" + countryName + "\n" + "-----" + "\n");
             }
         }
         return athletesInfo.toString();
     }
    
  3. 获取比赛结果

    //输出决赛每个运动项目结果
     public static String outputEventsResult(String sportName) throws IOException {
         //存储待输出的比赛结果
         StringBuilder eventsResult = new StringBuilder();
         //获取比赛结果的文件id
         String fileID = getResultId(sportName);
    
         if (fileID != "") {
             String file = "src\\data\\results\\" + fileID + ".json";
             JSONObject jsonObject = JSONObject.parseObject(getJsonInfo(file));
             JSONArray jsonArray = jsonObject.getJSONArray("Heats");
             for (int i = 0; i < jsonArray.size(); i++) {
                 JSONObject everyScore = jsonArray.getJSONObject(i);
                 if ("Final".equals(everyScore.getString("Name"))) {
                     //获取该项目的比赛结果
                     JSONArray results = everyScore.getJSONArray("Results");
                     for (int j = 0; j < results.size(); j++) {
                         JSONObject player = results.getJSONObject(j);
    
                         //该选手的全名(双人项目,输出名字时用“&”连接,按照选手名(Last Name)从小到大排序)
                         String fullName = player.getString("FullName");
                         if (fullName.contains(" / ")) {
                             String[] str = fullName.split(" / ");
                             List<String> doubleName = new ArrayList<>();
                             doubleName.add(str[0]);
                             doubleName.add(str[1]);
                             Collections.sort(doubleName);
                             fullName = doubleName.get(0) + " & " + doubleName.get(1);
                         }
    
                         eventsResult.append("Full Name:" + fullName + "\n");
                         //该选手的排名
                         eventsResult.append("Rank:" + player.getString("Rank") + "\n");
                         //该选手的具体成绩
                         eventsResult.append("Score:");
                         JSONArray scores = player.getJSONArray("Dives");
                         for (int k = 0; k < scores.size(); k++) {
                             JSONObject diveScore = scores.getJSONObject(k);
                             //获取每小节的成绩
                             eventsResult.append(diveScore.getString("DivePoints"));
                             int len = scores.size();
                             if (k < len - 1) {
                                 eventsResult.append(" + ");
                             } else {
                                 //获取总成绩
                                 eventsResult.append(" = " + diveScore.getString("TotalPoints") + "\n");
                             }
                         }
                         eventsResult.append("-----" + "\n");
                     }
                     return eventsResult.toString();
                 }
             }
         }
         return "";
     }
    
  4. 通过比赛项目名获得比赛结果文件的id

    //通过比赛项目名获得比赛结果文件的id
     private static String getResultId(String sportName) throws IOException {
         JSONObject jsonObject = JSONObject.parseObject(getJsonInfo(EVENTS_FILE));
         JSONArray jsonArray = jsonObject.getJSONArray("Sports");
         JSONObject sports = jsonArray.getJSONObject(0);
         JSONArray diving = sports.getJSONArray("DisciplineList");
         for (int i = 0; i < diving.size(); i++) {
             //获得该跳水项目的数据
             JSONObject divingSport = diving.getJSONObject(i);
             //获取该项目的项目名
             String str = divingSport.getString("DisciplineName").toLowerCase();
             //获取文件id
             if (str.equals(sportName)) {
                 return divingSport.getString("Id");
             }
         }
         return "";
     }
    
  5. 获取json文件的内容

    //获取json文件的内容
     private static String getJsonInfo(String path) throws IOException {
         StringBuilder sb = new StringBuilder();
         File file = new File(path);
         if (file.exists()) {
             FileReader fr = new FileReader(file);
             BufferedReader br = new BufferedReader(fr);
             String line = "";
             while ((line = br.readLine()) != null) {
                 sb.append(line);
             }
             br.close();
             fr.close();
             return sb.toString();
         } else {
             throw new FileNotFoundException("找不到文件" + path);
         }
     }
    

    性能改进

    引入缓存

    HashMap<String, String> maps = new HashMap<>();//缓存命令和对应的输出内容
    

    处理指令时,优先查看缓存是否有现成数据

    if (maps.containsKey(commands.get(i))) {
                     info.append(maps.get(commands.get(i)));
                     continue;
                 }
    

    处理首次出现的指令时,记得更新缓存数据

    maps.put("players", Lib.outputAthletesInfo());
    

    优化结果

    用10w条result women 10m platform指令测试,可以看到,引入缓存后,运行速度明显加快
    优化前:

    优化前


    优化后:

    优化后

    单元测试

    使用Junit进行单元测试,教程:IDEA单元测试
    测试代码:

    在这里插入图片描述


    覆盖率:

    在这里插入图片描述


    代码覆盖率均达到85%以上,符合单元测试标准

    异常处理

    代码:

    在这里插入图片描述


    在这里插入图片描述


    测试结果:

    在这里插入图片描述

    心得体会

    在完成作业的过程中,我认识到软件开发的整体流程,过去只想着代码如何跑通,但这次的作业不仅仅要跑通代码,还要考虑代码的可读性、复用率和性能等。从构思、规划到代码实现、性能测试,再到编写总结报告,完整的一套流程让我收获颇丰,我学会了如何用git管理项目文件,在IDEA进行单元测试,如何优化代码性能,对软件开发的意义有了更深的感悟。

...全文
51 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

122

社区成员

发帖
与我相关
我的任务
社区描述
FZU-SE
软件工程 高校
社区管理员
  • LinQF39
  • 助教-吴可仪
  • 一杯时间
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧