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

221900136_黄依灵 学生 2022-03-05 12:01:35
这个作业属于哪个课程2022年福大-软件工程、实践-W班
这个作业要求在哪里软件工程实践第二次作业——个人实战
这个作业的目标完成对冬奥会的赛事数据的收集,并实现一个能够对国家排名及奖牌个数统计的控制台程序
其他参考文献百度

目录

  • 一. Gitcode项目地址
  • 二. PSP表格
  • 三. 解题思路
  • 1.数据爬取
  • 2.文件读取
  • 3.解析json数据
  • 3.指令正确性判断
  • 四.接口设计和实现过程
  • 五.关键代码展示
  • 六.性能改进
  • 七.单元测试
  • 八.异常处理
  • 九.心得体会


一. Gitcode项目地址

仓库地址请点击这里


二. PSP表格

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

三. 解题思路

1.数据爬取

在这里插入图片描述

  • 进入冬奥官网 选择赛程,按键F12并刷新出现上图所示,点击网络,选择所需要的数据在新标签页打开,下载到本地data文件夹,这样更方便对于数据的读取和处理

  • 保存下来后,用vscode对文件格式化,对头尾进行处理,再用在线网站进行unicode中文转换,将转换后数据中的 " % / "全部替换为" / "

2.文件读取

使用BuilderReader.readLine()和BuilderWriter对文件进行读写,使用StringBuilder.append()将读取到的文件内容拼接

3.解析json数据

引入第三方库Gson,根据data文件夹中json数据的特点,因BufferReader.readLine()可读取一行的信息,所以当读取到" }," 或者 " }" 时,就可以用gson提取一个对象

Matches match = gson.fromJson(builder.toString(), Matches.class);

3.指令正确性判断

  1. 首先先判断是否为空行,若是空行跳过
  2. 判断是否为total,是的话则输出奖牌总榜
  3. 判断是否为schedule开头的字符串,若schedule后的日期并不在冬奥期间,则输出N/A,若指令正确,则 输出每日赛程
  4. 其余指令输出Error

四.接口设计和实现过程

共有四个类:OlympicSearch、Lib、Medals、Matches。

  • 其中Medals和Matches用于Gson匹配json数据,提取所需要的json数据
  • Lib用于处理total指令和schedule指令的输出,在Lib中读取json文件并通过Gson创建Medals和Matches对象,最终调用Medals.toString()和Matches.toString()将字符串存储在属性中,方便重复调用
  • OlympicSearch存放main函数,从cmd中读取input.txt和output.txt路径,并对指令正确性进行判断,可调用Lib类

OlympicSearch类

public class OlympicSearch
{
    //br用于读input.txt文件,bw用于写output.txt文件
    BufferedReader br;
    BufferedWriter bw;

    //根据传入的路径初始化 br和 bw
    public OlympicSearch(String input,String output)
    //获取 br
    public BufferedReader getBufferReader()
    //获取 bw
    public BufferedWriter getBufferWriter()
    //判断日期是否正确,传入的参数为用空格分割指令得到的字符串数组
    public static boolean rightDate(String[] strs)
    
    public static void main(String[] args)
}

Lib类

public class Lib
{
    //用于存储对应输出结果的字符串,以便重复指令可直接输出该字符串,提高性能
    String medalsString;
    Map<String,String> matchesString;
    BufferedWriter bw;
    
    //获取OlympicSearch的属性bw
    Lib(BufferedWriter bw)
    //读取date.json文件,将该日期下输出结果的字符串存储在matchesString中
    void initMatches(String date) throws IOException
    //读取total.json文件,将输出结果的字符串存储在medalsString中
    void initMeadls() throws IOException
    //main方法中在指令正确情况下调用该函数
    void printMedals() throws IOException
    //main方法中在指令正确情况下调用该函数
    void printMatches(String date) throws IOException
}

Medals类(用于Gson匹配json数据)

class Medals
{
    String rank;
    String countryid;
    String gold;
    String silver;
    String bronze;
    String count;
    
    //对得到的json数据进行处理
    public void changeData()
    //转换成字符串
    public String toString()
}

Matches类(用于Gson匹配json数据)

class Matches
{
    String startdatecn;
    String itemcodename;
    String title;
    String homename, awayname;
    String venuename;
    
    //对得到的json数据进行处理
    public void changeData()
    //转换成字符串
    public String toString()
}

五.关键代码展示

当输入指令为 schedule 0202 时,main函数会调用 Lib.printMatches("0202") 方法

  1. 首先通过 matchesString.containsKey("0202") 判断是否以前存储过0202这一天的输出内容
  2. 若无就需要调用 Lib.initMatches("0202") 从 0202.json 文件中提取数据并转换为字符串形式存储到 matchesString 中
  3. 向 output.txt 中写入 matchesString.get("0202")
void printMatches(String date) throws IOException
    {
        if(!matchesString.containsKey(date))
        {
            try
            {
                initMatches(date);
            }
            catch (IOException e)
            {
                System.out.println("读取"+date+".json文件失败");
            }
        }
        bw.write(matchesString.get(date));
        bw.flush();
    }

此处用到Gson并不是直接对整个json数组解析 ,而是通过 StringBuilder 和 BufferedReader 将json文件划分成一个个对象,转换为一个个Matches类,逐个存储到 List<Matches> matchesList中,最后用for循环将matchesList中的每个Matches通过调用 Matches.toString() 方法得到的字符串拼接在一起,赋值给 matchesString

void initMatches(String date) throws IOException
    {
        Gson gson = new Gson();
        String content = "";
        List<Matches> matchesList=new ArrayList<Matches>();
        
        StringBuilder builder = new StringBuilder();
        String filePath = "src\\data\\schedule\\" + date + ".json";
        File file = new File(filePath);
        if (!file.exists()) System.out.println("找不到文件"+date+".json");
        InputStreamReader streamReader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8);
        BufferedReader bufferedReader = new BufferedReader(streamReader);
        
        //对读取到的json数据划分成单个对象的字符串,再用gson转化成match类
        while ((content = bufferedReader.readLine()) != null)
        {
            if (content.equals("            },") || content.equals("            }"))//根据”...“划分
            {
                builder.append("            }");
                Matches match = gson.fromJson(builder.toString(), Matches.class);
                match.changeData();
                matchesList.add(match);
                builder.delete(0, builder.length());//每提取完一个match类,对builder读取到的内容清空
                if (content.equals("            }")) break;
            } else builder.append(content);
            if (content.equals("        \"matchList\": [")) builder.delete(0, builder.length());//将无效内容删除
        }
        //将所需的输出内容存储到Map<String,String> matchesString中
        builder.delete(0, builder.length());
        for (int i=0;i<matchesList.size();i++)
        {
            builder.append(matchesList.get(i).toString());
            if(i<matchesList.size()-1) builder.append("\n");
        }
        matchesString.put(date,builder.toString());
        
        bufferedReader.close();
        streamReader.close();
    }

当输入指令为total时,main函数调用Lib.printMedals()方法
相关部分代码与输入指令为 schedule 0202相似,就不再细说

main函数流程图

在这里插入图片描述

六.性能改进

  • 原本的代码
    将数据存储在 Map<String,List<Matches>> matchesMapList<Medals> medalsList 中,每次输出结果都需要用for循环遍历list和map中的每个Medals和Matches,这样对于相同的指令就很浪费时间
  • 性能改进后
    对于total指令,直接将需要输出的字符串存储在 String medalsString中,此后再遇到total指令,直接输出medalsString
    对于schedule 0215指令,将需要输出的字符串存储在Map<String,String> matchesString
    中,键对应日期,值对应指令需要输出的字符串,此后再遇到该指令,直接输出 matchesString.get(date)
    因此,对于重复的指令不需要再读取json文件和存储数据,直接提取字符串

用包含3000条指令的input.txt测试代码运行情况:

改进前:

在这里插入图片描述

改进后:

在这里插入图片描述

性能分析

改进前:

在这里插入图片描述

改进后:

在这里插入图片描述

七.单元测试

以下为部分单元测试代码

    @Test 
    public void testrightDate()
    {
        search=new OlympicSearch("E:\\软件工程\\OlympicSearch\\input.txt","E:\\软件工程\\OlympicSearch\\output.txt");
        lib=new Lib(search.getBufferWriter());
        try 
        {
            lib.printMedals();
            lib.initMatches("0202");
            lib.printMatches("");
            lib.initMatches("0201");
        } 
        catch (IOException e) 
        {
            e.printStackTrace();
        }
    }
    @Test
    public void testrightDate()
    {
        assertEquals(true,OlympicSearch.rightDate(new String[]{"schedule","0202"}));
        assertEquals(true,OlympicSearch.rightDate(new String[]{"schedule","0220"}));
        assertEquals(true,OlympicSearch.rightDate(new String[]{"schedule","0213"}));
        assertEquals(true,OlympicSearch.rightDate(new String[]{"schedule"," ","0202"}));
        assertEquals(false,OlympicSearch.rightDate(new String[]{"schedule","大时代"}));
        assertEquals(false,OlympicSearch.rightDate(new String[]{"schedule","asadsa"}));
    }
    public void testOlympic()
    {
        OlympicSearch search1=new OlympicSearch("E:\\软件工程\\OlympicSearch\\input.txt","E:\\软件工程\\OlympicSearch\\output.txt");
        OlympicSearch search2=new OlympicSearch("E:\\软件工程\\OlympicSearch\\i1.txt","E:\\软件工程\\OlympicSearch\\t.txt");
    }    

在这里插入图片描述

八.异常处理

  • 大部分函数都用throws抛出异常,然后在main函数中用 try-catch 处理
    void initMeadls() throws IOException
  • 少部分的函数会直接在内部直接用 try-catch 处理
    File file = new File("src\\data\\total.json");
    if (!file.exists()) System.out.println("找不到文件total.json");
    public OlympicSearch(String input,String output)
    {
        try
        {
            InputStream in=new FileInputStream(input);
            OutputStream out=new FileOutputStream(output);
            br=new BufferedReader(new InputStreamReader(in,"UTF-8"));
            bw=new BufferedWriter(new OutputStreamWriter(out,"UTF-8"));
        }
        catch(FileNotFoundException e)
        {
            System.out.println("文件找不到");
        }
        catch (UnsupportedEncodingException e)
        {
            System.out.println("转码异常");
        }
    }

九.心得体会

  • 这次作业本质上是对文件的读取和写入,写代码的时间不长,但是处理bug的时间很长,印象最深刻的就是在cmd运行程序,明明在idea都能正常运行,但是在cmd运行就会输出结果乱码和报错,百度了一天倒是对idea和cmd的配置有了更多了解,最后没有在百度上找到答案,但是在同学和助教的帮助下解决了bug
  • 还有关于软件的配置问题也要很小心,比起写代码出问题,那些配置出了问题会让人更害怕,其实对于软件的配置还不够了解,在百度的时候很多东西都是懵懵懂懂的
  • 作业一定要提早写,要留充分的时间处理bug,快要到ddl的时候处理bug的状态会差很多
  • 软件开发的时候,沟通了解需求是非常有必要的。比如在群里,大家向助教询问各种指令对应的输出情况后,才对需求有了更深入的了解。同时,需求是容易变动的,所以在编写代码的过程中,对函数要做好划分,这样才能在需求变动的情况下方便代码的变更
  • 这次作业由于对java的知识都忘得差不多了,所以花了很多时间在查询资料上,还是要勤加编写代码
...全文
155 2 打赏 收藏 举报
写回复
2 条回复
切换为时间正序
请发表友善的回复…
发表回复
Jingbin-Wang 教师 03-05
  • 打赏
  • 举报
回复
3000条指令的测试效果上看改进的效果不是很明显,如果再上两个数量级看看改进的效果会不会明显一点?
221900136_黄依灵 学生 03-06
  • 举报
回复
@FZU_SE_teacherW 用11万条指令测试:改进前 程序运行时间: 125358ms 改进后 程序运行时间: 8054ms
发帖
2022年福大-软件工程、实践-W班

136

社区成员

2022年福大-软件工程;软件工程实践-W班
软件工程 高校
社区管理员
  • FZU_SE_teacherW
  • 丝雨_xrc
  • Lyu-
加入社区
帖子事件
创建了帖子
2022-03-05 12:01
社区公告
暂无公告