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

221900214何震东 学生 2022-03-04 11:34:50
这个作业属于哪个课程2022年福大-软件工程、实践-W班
这个作业要求在哪里软件工程实践第二次作业——个人实战
这个作业的目标完成对冬奥会的赛事数据的收集,并实现一个能够对国家排名及奖牌个数统计的控制台程序。
其他参考文献Fastjson 简明教程

目录

目录

  • Gitcode项目地址
  • PSP表格
  • 解题思路描述
  • 获取数据
  • 解析json
  • 读取input.txt的指令
  • 接口的设计与实现过程
  • 涉及类
  • 主要函数
  • 函数调用关系
  • 关键函数流程图
  • 关键代码展示
  • 入口主函数代码
  • 获取input.txt指令并转换成String列表
  • 验证指令正确性并返回结果
  • 性能改进
  • 改进前性能分析
  • 改进思路
  • 改进后性能分析
  • 单元测试
  • 异常处理
  • UnsupportedEncodingException
  • IOException
  • FileNotFoundException
  • 心得体会


Gitcode项目地址

项目地址

PSP表格

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

解题思路描述

【该爬取行为仅用于课程教学】

获取数据

在东奥专栏打开每日赛程,在浏览器用F12打开开发者工具,选择网络按钮,点击对应的日期

img

分析请求标头可以得到发送GET请求的链接
爬取数据我使用python里的requests包的get(url)方法读出response,
将用response.text()读出返回文本数据r
用r[3:-2]掐头去尾,并且将r字符串中的"\/"替换为"/"
然后利用r.encode("utf8").decode('unicode_escape')语句将r从Unicode编码转为utf8
最后写入data文件夹中
代码如图

img

解析json

编写两个实体类Medal和Match用来分别接收奖牌和比赛数据

img

img

利用第三方jar包fastjson进行解析得到Medal列表如下,Match也是类似

img

读取input.txt的指令

利用输入流将input的指令全部读入
利用String类的split("\\\n")方法将读入的字符串变成String列表
将指令首尾空格去掉后判断首字符是否为t或s,否的话输出Error
否则进一步判断是否是total,如是输出总榜单,不是则判断前面八个字符是否是schedule,否则输出Error,是的话判断后面是否带有空格加指定范围内的日期,是则输出对应日期的比赛列表,否则输出N/A


接口的设计与实现过程

涉及类

public class OlympicSearch {}
//调用Lib类的函数从input.txt中读取指令,验证指令,根据指令输出选择对应输出
public class Lib {}
//Lib类,提供所有的工具函数,包括读取文件函数,读取指令函数,写入文件函数,json解析函数,输出格式控制函数,验证指令函数
public class Match {}
//用来存放从json数据读取的赛事信息,位于pojo软件包下
public class Medal {}
//用来存放从json数据读取的奖牌榜信息,位于pojo软件包下

主要函数

OlympicSearch类

public static void main(String[] args) {}
//程序入口,调用函数,读入指令,并且进行判断及输出

Lib类

public String readFile(String fileName){}
//读取data下的json文件
public String readInputTxt(String fileName){
//读取input.txt文件
public void outputFile(String fileName,String content){}
//将指定内容输出对应文件
public void deleteFile(String fileName){}
//删除指定文件
public List<String> getInstructionList(String fileName){}
//从input.txt文件读取指令并将其转换为List<String>类型
public List<Medal> readTotal(String totalFIle){}
//读取total.json文件并将其解析为List<Medal>
public List<Match> readMatch(String matchFile){}
//读取schedule XXXX.json文件并将其解析为List<Match>
public String getOutputTotal(List<Medal> medalList){}
//List<Medal>类型的列表转换成题目要求输出格式
public String getOutPutMatch(List<Match> matchList){}
//List<Match>类型的列表转换成题目要求输出格式
public String validateInstruction(String instruction){}
//验证指令

函数调用关系

img

关键函数流程图

img

关键代码展示

入口主函数代码

代码开始,如果给定的输出文件已存在,先删除,
然后遍历指令,验证指令,按照对应指令得到对应输出值,拼接到存放文件输出字符串outputContent中,
拼接一定数量后outputContent输出到指定输出文件,
对于合法指令,先判断是否在hashMap中存在,存在则直接从hashMap中取
不存在则将其放到hashmap中

Lib lib = new Lib();
if (!lib.deleteFile(outputFile)) {
    return;
}
Map<String,String> map = new HashMap<>();
StringBuilder outputContent = new StringBuilder("");
List<String> instructionList =  lib.getInstructionList(inputFile);
int cnt = 0;
for (String instruction : instructionList) {
    cnt++;
    String validateResult = lib.validateInstruction(instruction);
    if (validateResult.equals("total")) {
        String outputTotal = "";
        if (map.containsKey("total")) {
            outputTotal = map.get("total");
        }
        else {
            List<Medal> medalList = lib.readTotal(totalFIle);
            outputTotal = lib.getOutputTotal(medalList);
            map.put("total",outputTotal);
        }
        outputContent.append(outputTotal);
    }
    else if (validateResult.equals("Error")||validateResult.equals("N/A")) {
        outputContent .append(validateResult).append("\n-----\n");
    }
    else {
        String date = validateResult;
        String outputMatch = null;
        if (map.containsKey(date)) {
            outputMatch = map.get(date);
        }
        else {
            outputMatch = lib.getOutPutMatch(lib.readMatch(matchFilePrefix+date+".json"));
            map.put(date,outputMatch);

        }
            outputContent.append(outputMatch);
    }
    if (cnt > 10000 ) {
        lib.outputFile(outputFile,outputContent.toString());
        outputContent = new StringBuilder("");
        cnt = 0;
    }

}
lib.outputFile(outputFile,outputContent.toString());

获取input.txt指令并转换成String列表

将输入指令全部读入,然后利用split("\\\n")分割出每一行,
然后将分割出的每行用strip()将收尾空格去除,如果结果为空则说明此行为空行,跳过
否则放到String列表中,最后将String列表返回。

public List<String> getInstructionList(String fileName) {
    List<String> instructionList = new ArrayList<>();
    String content = readInputTxt(fileName);
    for (String s : content.split("\\\n")) {
        s=s.strip();
        if (!s.equals(""))
            instructionList.add(s);
    }
    return instructionList;
}

验证指令正确性并返回结果

参数传入已去除首尾空格的指令列表
初始化验证结果为Error,判断指令首字母是否为t或者s,
如果为t,判断其是否是total,是则返回total,否则返回Error
如果为s判断指令长度是否小于12或者前八个字符是否为schedule,
如果是则返回Error,否则进一步判断schedule后面是有空格
无则返回Error,有则进一步判断其是否符合指定的日期格式
否则返回N/A,是则返回对应的日期
最后特判一下指令是schedule这种情况,是则返回N/A

public String validateInstruction(String instruction) {
    instruction = instruction.strip();
    String validateAns = "Error";
    if (instruction.substring(0,1).equals("t")) {
        if (instruction.length()!=5||(!instruction.equals("total")))
            validateAns = "Error";
        else
            validateAns = "total";
    }
    else if(instruction.substring(0,1).equals("s")) {
        if (instruction.length()<12||(!instruction.substring(0,8).equals("schedule")))
            validateAns = "Error";
        else {
            String date = instruction.substring(8,instruction.length());
            if (!date.substring(0,1).equals(" "))
                validateAns = "Error";
            else {
                date = date.strip();
                if (date.length()!=4 || (!date.matches("\\d\\d\\d\\d")))
                    validateAns = "N/A";
                else if (!((date.matches("020[2-9]"))||date.matches("021[0-9]")||date.matches("0220")))
                    validateAns = "N/A" ;
                else
                    validateAns = date;
            }
        }
        if (instruction.equals("schedule"))
            validateAns = "N/A";
    }
    return validateAns;
}

性能改进

改进前性能分析

用一百万行有效指令测试,跑了四十几分钟都停不下来,内存最高消耗4、5G,写了个3A大作,电脑原地起飞

img

改进思路

经过对各部分代码的时间测速,我发现每次从json文件读入进行解析以及String的拼接耗费时间大,于是我用hashmap将已访问过的数据储存到内存,若重复访问时则直接到hashmap取出,
并且对于次数很高的字符串拼接不采用String+String的形式,而采用StringBuilder.append()。
并且将输出数据拼接到一定大小才输出,而不是一读指令就输出。

改进后性能分析

时间变成11秒,内存消耗变成两三百M

img

img

单元测试

主要对验证函数进行测试

img

覆盖率测试

img

异常处理

UnsupportedEncodingException

输入流不支持相应编码时会出错,这个异常在读入文件并指定编码时可能会出现

img

IOException

文件读写可能会出现异常

img

FileNotFoundException

文件找不到异常,指定读写的文件时可能会出现该异常

img


心得体会

不知道怎么开始做作业的时候,可以先写个大概的博客,确定大致的步骤
遇到问题不过分盲目调试,应学会百度
应学会如何定位问题,学会测试代码各个部分的大致运行时间进行优化
应一开始思考大致的写法,应避免在后期再大改特改
不要过分相信自己的时间预估能力,应多安排点时间灵活机动写作业

...全文
125 回复 打赏 收藏 举报
写回复
回复
切换为时间正序
请发表友善的回复…
发表回复
发帖
2022年福大-软件工程、实践-W班

136

社区成员

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