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

这个作业属于哪个课程https://bbs.csdn.net/forums/ssynkqtd_06?typeId=6299198
这个作业要求在哪里https://bbs.csdn.net/topics/618087255
这个作业的目标完成对世界游泳锦标赛跳水项目相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序
其他参考文献构建之法、单元测试教程、Java开发手册、菜鸟教程

Gitcode项目地址

https://gitcode.net/CEOLucifer/project-java

PSP表格

阶段预估耗时(分钟)实际耗时(分钟)
计划
· 估计这个任务需要多少时间2020
开发
· 需求分析(包括学习新技术)180200
· 生成设计文档3020
· 设计复审3040
· 代码规范2030
· 具体设计6060
· 具体代码480800
· 代码复审3060
· 测试(自我测试,修改代码,提交修改)250600
报告
· 测试报告4060
· 计算工作量2530
· 事后总结,并提出过程改进计划120120
合计12852040

解题思路描述

获取数据

数据使用了助教提供的相关数据

分析json结构,编写对应数据类

athletes.json

是一个数组,每个元素对应一条记录,这个记录包含国家、这个国家的选手数组。

根据这个文件,我们可以获取所有选手的信息。

而这些信息正是功能1实现的基础

根据问题域和系统责任,得到对应类图如下:

在这里插入图片描述

有了这些类,我可以很轻松地将这个json文件反序列化出对应的对象,并根据这些对象进行处理。

event.json

是一个对象。Sports字段是一个数组,而这个数组只有一个元素,这个元素的DisciplineList字段是一个数组,其每一个元素就是一个项目,如Women 1m Springboard、Women 3m Springboard、Women 10m Platform等等,包含了一个项目的重要数据,如Id(用于找到项目具体数据的对应的json文件),还有HeatList(一个数组,包含这个项目有哪些比赛阶段)。

综合分析来看,似乎只有DisciplineList是有用的数据,而其他的数据都无关紧要,并不符合需求,因此只要序列化出这个DisciplineList即可。

根据问题域和系统责任,得到对应类图如下:

在这里插入图片描述

results文件夹下的各个.json文件

是一个对象,表示一个项目的具体数据。

文件名正好是对应项目的Id。

  • DisciplineName是项目名称。
  • Heats是一个数组,每个元素表示这个项目的一个阶段。
    • PhaseName是阶段名。
    • Results是一个数组,每个元素是一个选手的结果。
      • TotalPoints是总分
      • Rank是排名
      • FullName是选手姓名
      • Dives是一个数组,每个元素是一次跳水的数据
        • DivePoints是本次跳水的分数
        • JudgesScores是数组,每个元素是一个裁判的评分。这个数据其实是没有用处的,应该忽略。

这些数据是功能2的基础

根据问题域和系统责任,得到对应类图如下:

在这里插入图片描述

接口设计和实现过程

根据问题域和系统责任,得到核心模块Core的类图如下:

在这里插入图片描述

该图展示了Core对外提供的接口。

  • OutputAllPlayers()

    1. 读取Country和Participation数据;
    2. 合成字符串:遍历participants,获取每一个选手的信息,按照输出要求,合成字符串;
    3. 写入到output.txt。
  • OutputFinalResultOf()

    1. 获取相应项目数据;
    2. 查找Finals数据;
    3. 合成字符串:读取每一个Result,按照输出要求,合成字符串;
    4. 写入到output.txt。

关键代码展示

    /**
     * 输出所有选手信息到output.txt
     */
    static public void OutputAllPlayers() {

        if (isOutputAllPlayersStringCached) // 如果有缓存,输出
        {
            FileHelper.WriteAllText(Config.instance.outputPath, outputAllPlayersCacheString);
            return;
        }

        LoadCountries();
        LoadParticipations();

        String content = "";

        for (Participation participation : participations) {
            content += "Full Name:" + participation.PreferredFirstName + " " +
                    participation.PreferredLastName + "\n";

            if (participation.Gender == 0)
                content += "Gender:Male\n";
            else
                content += "Gender:Female\n";

            content += "Country:" + participation.NAT + "\n";
            content += "-----\n";
        }

        FileHelper.WriteAllText(Config.instance.outputPath, content);

        // 缓存
        outputAllPlayersCacheString = content;
        isOutputAllPlayersStringCached = true;
    }

    /**
     * 输出指定项目的决赛结果
     * 
     * @param event_name
     * @return 若有Final结果,返回true;若没有该项目,或者该项目没有Final结果,返回false。
     */
    static public boolean OutputFinalResultOf(String event_name) {
        LoadDisciplineMap();

        if (!disciplineMap.containsKey(event_name)) // 没有这个项目
        {
            OutputNA();
            return false;
        }

        LoadDiscipline2(event_name);

        // 读取比赛结果文件
        Discipline2 discipline2 = discipline2Map.get(event_name);

        // 获取Finals结果们
        List<Result> results = null;
        for (Heat2 each : discipline2.Heats) {
            if (each.PhaseName.equals("Finals")) {
                results = each.Results;
                break;
            }
        }

        // 主要的输出逻辑
        String content = "";

        for (Result result : results) {
            content += "Full Name:" + result.FullName + "\n";
            content += "Rank:" + result.Rank + "\n";

            String score_str = "Score:";
            int length = result.Dives.size();
            for (int i = 0; i < length - 1; ++i)
                score_str += result.Dives.get(i).DivePoints + " + ";
            score_str += result.Dives.get(length - 1).DivePoints + " = " + result.TotalPoints + "\n";

            content += score_str;
            content += "-----\n";
        }

        FileHelper.WriteAllText(Config.instance.outputPath, content);
        return true;
    }

性能优化

优化前

存在以下问题,会对程序的性能造成显著影响:

  1. 完全相同的命令,结果一般是一样的。但是,程序在每次处理他们的时候完全是从零开始的,存在重复的工作。
  2. 读取文件时,并没有缓存读取结果,一个文件可能被重复读取了好几次。

优化过程和思路

对于一个命令,都会有对应输出,且命令和输出时一一对应的。

基于这个原理,用一个字典,键表示命令字符串,值表示输出字符串。当存在遇到已经处理过的命令时,程序直接取出结果作为输出;否则,缓存该执行结果于字典。

但是一般结果内存占用比较大,属于空间换时间。

优化后

优化前后的性能对比:

  1. 处理1000次players命令
    @Test
    public void TestOutputAllPlayers_1000() {
        for (int i = 0; i < 1000; ++i)
            Core.OutputAllPlayers();
    }
  • 优化前

    在这里插入图片描述

    用时931ms。

  • 优化后

    在这里插入图片描述

    用时282ms。

  1. 处理重复的result命令
    @Test
    public void TestRepeatCommand() {
        for (int i = 0; i < 2000; ++i)
            Core.OutputFinalResultOf("women 1m springboard");
        for (int i = 0; i < 2000; ++i)
            Core.OutputFinalResultOf("men 1m springboard");
    }
  • 优化前

在这里插入图片描述

用时5063ms

  • 优化后

在这里插入图片描述

用时680ms

单元测试

public class CoreTest {

    @Before
    public void BeforeEach() {
        Config.LoadConfig();
        FileHelper.DeleteThenCreateFile(Config.instance.outputPath);
    }

    @Test
    public void TestOutputAllPlayers() {
        Core.OutputAllPlayers();
    }

    @Test
    public void TestOutputAllPlayers_1000() {
        for (int i = 0; i < 1000; ++i)
            Core.OutputAllPlayers();
    }

    @Test
    public void TestRepeatCommand() {
        for (int i = 0; i < 2000; ++i)
            Core.OutputFinalResultOf("women 1m springboard");
        for (int i = 0; i < 2000; ++i)
            Core.OutputFinalResultOf("men 1m springboard");
    }

    // #region 断言true
    @Test
    public void Test_1() {
        assertEquals(Core.OutputFinalResultOf("women 1m springboard"), true);
    }

    @Test
    public void Test_2() {
        assertEquals(Core.OutputFinalResultOf("women 3m springboard"), true);
    }

    @Test
    public void Test_3() {
        assertEquals(Core.OutputFinalResultOf("women 10m platform"), true);
    }

    @Test
    public void Test_4() {
        assertEquals(Core.OutputFinalResultOf("women 3m synchronised"), true);
    }

    @Test
    public void Test_5() {
        assertEquals(Core.OutputFinalResultOf("women 10m synchronised"), true);
    }

    @Test
    public void Test_6() {
        assertEquals(Core.OutputFinalResultOf("men 1m springboard"), true);
    }

    @Test
    public void Test_7() {
        assertEquals(Core.OutputFinalResultOf("men 3m springboard"), true);
    }

    @Test
    public void Test_8() {
        assertEquals(Core.OutputFinalResultOf("men 10m platform"), true);
    }

    @Test
    public void Test_9() {
        assertEquals(Core.OutputFinalResultOf("men 3m synchronised"), true);
    }

    @Test
    public void Test_10() {
        assertEquals(Core.OutputFinalResultOf("men 10m synchronised"), true);
    }
    // #endregion

    // #region 断言false

    @Test
    public void Test_11() {
        assertEquals(Core.OutputFinalResultOf("what the hell is that"), false);
    }

    @Test
    public void Test_12() {
        assertEquals(Core.OutputFinalResultOf("men 99999m synchronised"), false);
    }

    @Test
    public void Test_13() {
        assertEquals(Core.OutputFinalResultOf("men 114514m synchronised"), false);
    }

    @Test
    public void Test_14() {
        assertEquals(Core.OutputFinalResultOf("men 1919810m synchronised"), false);
    }

    @Test
    public void Test_15() {
        assertEquals(Core.OutputFinalResultOf("boom"), false);
    }
    // #endregion
}

单元测试设计的分析

  1. 首先,测试覆盖了对各种项目决赛结果的输出。这包括了正确项目名和错误项目名的测试。
  2. 其次,包含了输出性能的测试。TestOutputAllPlayers_1000,TestRepeatCommand分别包含了对大量重复命令的性能测试。

异常处理

  1. 文件不存在

这是程序中主要的异常。数据文件的路径是用配置文件配置的,如果填写的路径不正确,会抛出异常。

    /**
     * 读取文件的全部文本
     * 
     * @param file_path
     * @return
     */
    static public String ReadAllText(String file_path) {
        String encoding = "UTF-8";
        File file = new File(file_path);
        Long filelength = file.length();
        byte[] filecontent = new byte[filelength.intValue()];
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            fis.read(filecontent);
            return new String(filecontent, encoding);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                if (fis != null)
                    fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 将文本追加写入到指定文件
     * 
     * @param file_path
     * @param content
     * @return
     */
    static public void WriteAllText(String file_path, String content) {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file_path, true);
            fos.write(content.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fos != null)
                    fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 对于指定路径,创建一个文件。如果路径的原先文件存在,先删除。
     * 
     * @param file_path
     */
    static public void DeleteThenCreateFile(String file_path) {
        File file = new File(file_path);
        if (file.exists())
            file.delete();
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

心得体会

  • 遇到的难点:

    1. json文件的解析。这次作业的json文件数据量太大,而且结构比较复杂。
    2. 单元测试。我之前没有进行过单元测试的编写的经验。
    3. 打包jar。
  • 学到的东西:

    1. git的使用更加熟练,学习了fork和发起pull request。
    2. 单元测试的概念和实现。
    3. 打包jar。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值