软件工程第二次作业——个人实战

222200108朱元烨 2024-09-10 12:04:48
这个作业属于哪个课程我们的软件工程社区
这个作业要求在哪里软件工程实践第二次作业
这个作业的目标完成对2024年巴黎奥运会相关数据的收集,并实现一个能够对国家排名及奖牌个数统计的控制台程序。
其他参考文献
##目录

目录

  • 1.项目地址
  • 2.PSP表格
  • 3.解题思路描述
  • 4.设计与实现过程
  • 功能2:输出每日赛程
  • 主函数
  • 接口封装想法
  • 5.程序性能改进
  • 6.单元测试展示
  • 7.心路历程与收获

1.项目地址

项目地址

2.PSP表格

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

3.解题思路描述

因为JAVA皆对象,所以如果能把网页爬取的数据转化为Java的对象的话,文件的读入和输出就不成问题
对于JAVA来说,json文件是比较常用的数据,通过依赖,使用FastJson中的函数,可以做到读入json文件并且输出json文件

4.设计与实现过程

我们从代码的功能先分析,首先我们要获得信息,那么我们就需要爬取2024巴黎奥运会的数据,要怎么获取数据呢,以获取每个国家的奖牌信息为例子,我的方法是在网页中点击“检查”

请添加图片描述

请添加图片描述

请添加图片描述


这样就得到了对应的json文件
同样的,也可以使用谷歌的扩展插件

请添加图片描述


对数据进行爬取
但是要注意的是,该插件导出的EXCEL表格可能需要去某些网站上转化成json文件才比较方便使用

在这里插入图片描述

本人因为自己实力不太行,爬出来的数据奇奇怪怪的,有些日期和时间甚至是分开的,所以我先在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();
        }
    }

这段代码的作用是读取指定路径的文件,将文件的全部内容作为一个字符串返回。简单解说一下:

  1. 打开文件进行读取。
  2. 逐行读取文件的内容。
  3. 把所有读取到的内容合并成一个字符串。
  4. 关闭文件并返回这个字符串。
    这样的方法就非常有利于我们读取 JSON 文件,并把文件内容以字符串的形式返回作后续处理。
    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格式进行输出。

    请添加图片描述

    ### 处理命令 ```javascript 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());
}

}

可读取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());
        }

像我这种菜鸟一般就是从结果推出过程。

功能2:输出每日赛程

**解析指令:** 首先对 schedule MMHH的指令进行解析,先要识别schedule 并且后面要获得有关日期的信息 将 command 字符串按空格分割成两部分,第一部分是指令(如 schedule),第二部分是日期(如 0811)。 如果分割后的数组长度不是 2,输出错误信息并返回。(格式有问题) 取出第二部分作为日期字符串 date
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 用的还是对接口封装前的函数

总结:这个作业的完成由五个函数完成
逻辑顺序:

请添加图片描述

5.程序性能改进

在这里对除了文件读入输出函数进行性能改进

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());
        }

6.单元测试展示

请添加图片描述

7.心路历程与收获

太难了,之前都没有这么具体地使用idea,而且完成像单元测试和性能优化这种改动,json文件的爬取也是向别的同学请教的,基本上感觉是从零开始边学边做,学到了挺多东西,接口有想法去实现但感觉能力不够QAQ

img

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

111

社区成员

发帖
与我相关
我的任务
社区描述
202401_CS_SE_FZU
软件工程 高校
社区管理员
  • FZU_SE_TeacherL
  • 言1837
  • 防震水泥
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

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