这个作业属于哪个课程 | <班级链接> |
---|---|
这个作业要求在哪里 | <作业要求的链接> |
这个作业的目标 | github初使用、代码规范制定、疫情统计 |
作业正文 | 本文 |
其他参考文献 | commit message书写指南,.gitignore配置,Github使用,idea Junit使用 |
1、Github仓库地址
2、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 60 |
Estimate | 估计这个任务需要多少时间 | 30 | 60 |
Development | 开发 | 900 | 960 |
Analysis | 需求分析 (包括学习新技术) | 150 | 120 |
Design Spec | 生成设计文档 | 30 | 30 |
Design Review | 设计复审 | 60 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 60 | 30 |
Design | 具体设计 | 90 | 60 |
Coding | 具体编码 | 300 | 420 |
Code Review | 代码复审 | 30 | 30 |
Test | 测试(自我测试,修改代码,提交修改) | 180 | 240 |
Reporting | 报告 | 150 | 70 |
Test Repor | 测试报告 | 30 | 20 |
Size Measurement | 计算工作量 | 60 | 20 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 60 | 30 |
合计 | 1080 | 1100 |
3、解题思路
- 查看附录教程里提供的文章,了解Github的基本使用并安装Github Desktop。
- 阅读《构造之法》1~3章。
- 克隆仓库代码并了解日志文件结构。
日志文件以日期命名,只要将文件排序就可以控制统计到哪一天
- 设计类结构和功能。
可以设计一个类专门用于存储一个省的ip、cure、sp、dead数据.
- 编码思路:
将参数按空格切分后可分别获得date、type、province、log、out等参数值然后读取日志文件获得每一条记录,统计记录的值。之后再按照type和province的值有选择性地输出数据到文件中。
- 设计一个类Record保存每个省份的ip,sp,dead,cure 的数据
- 运用数据结构TreeMap<String,Record>保存province到Record的映射
- 设计MapKeyComparator作为TreeMap排序的比较器
根据日志文件,每条记录的格式为:
- <省> 治愈 n人
- <省> 死亡 n人
- <省> 新增 感染患者 n人
- <省> 新增 疑似患者 n人
- <省> 疑似患者 确诊感染 n人
- <省> 排除 疑似患者 n人
- <省1> 感染患者 流入 <省2> n人
- <省1> 疑似患者 流入 <省2> n人
按空格拆分后按长度划分可以分为长度为3、4、5的字符串数组,分别处理各种情况即可得到各个省份的数据。得到数据后put进入TreeMap即可自动排序
4、设计实现过程
程序模块:
关键函数调用流程:
5、代码说明
- 文件列表获取
String[] getFileList(String path){
File file = new File(path);
String[] list = file.list(); //获取文件夹内的所有文件名
if (list != null) {
for(int i=0;i<list.length;i++){//拼凑成完整路径
list[i] = path + list[i];
}
return list;
}
else {
System.out.println("文件路径错误,找不到文件夹");
return null;
}
}
getFileList()通过日志文件夹路径获取文件夹下的所有文件名然后加上文件夹的路径形成完整的文件路径名例如:E:log下有两个文件 2020-01-28.log.txt和2020-02-05.log.txt则处理之后将变成E:log2020-01-28.log.txt和E:log2020-02-05.log.txt。之后,将所有文件名放入list中返回即可。
- 数据分类统计
void statisticData(String path,String date,String[] fileList,Map<String,Record> result){
date = path + date + ".log.txt";
for(String fileName : fileList){
if(date.compareTo(fileName) >= 0){//只统计date之前的数据
try {
FileReader fr = new FileReader(fileName);
BufferedReader bf = new BufferedReader(fr);
String line;
while ((line = bf.readLine()) != null) { // 按行读取字符串并统计信息
if(line.startsWith("//"))//忽略文件里的注释
continue;
String info[] = line.split(" "); //以空格将一行分割
switch (info.length){
case 3:{
countDeadAndCure(info,result);//统计治愈和死亡
};break;
case 4:{
countIpAndSp(info,result);//统计新增加的感染患者和疑似患者,确诊的,排除的患者
};break;
case 5:{
countMovedIpAndSp(info,result);//统计流动的感染患者和疑似患者
};break;
default:System.out.println(line+": 存在格式错误,此条记录无法处理");
}
}
bf.close();
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 遍历fileList获得并形成一个以当前日期命名的日志文件名,如今天是2020-02-15则字符串为2020-02-15.log.txt之后直接进行字符串的比较,小于等于这个字符串的文件将被统计。
- 对于每一条记录,以空格分割后形成长度分别为3,4,5的字符数组进行分类处理。
- 记录处理——记录处理大同小异,这里以countMovedIpAndSp()为例
void countMovedIpAndSp(String[] info,Map<String,Record> result){
int number = Lib.getNumber(info[info.length-1].substring(0,info[info.length-1].length()-1));//获得人数
String sourceProvince = info[0];
String aimProvince = info[3];
String ip = "感染患者",sp = "疑似患者";
if(info[1].equals(ip)){//感染患者流入
if(!result.containsKey(aimProvince)){//未出现过的省份
Record newRecord = new Record();//加上数据
newRecord.countInfection(number);
result.put(aimProvince,newRecord);
}
else {
Record tmpRecord = result.get(aimProvince);//加上数据
tmpRecord.countInfection(number);
result.put(aimProvince,tmpRecord);
}
Record aRecord = result.get(sourceProvince);//减去数据
aRecord.countInfection(-number);
result.put(sourceProvince,aRecord);
}
else if(info[1].equals(sp)){//疑似患者流入
if(!result.containsKey(aimProvince)){//未出现过的省份
Record newRecord = new Record();//加上数据
newRecord.countSuspected(number);
result.put(aimProvince,newRecord);
}
else {
Record tmpRecord = result.get(aimProvince);//加上数据
tmpRecord.countSuspected(number);
result.put(aimProvince,tmpRecord);
}
Record aRecord = result.get(sourceProvince);//减去数据
aRecord.countSuspected(-number);
result.put(sourceProvince,aRecord);
}
else {
System.out.print("此纪录无法处理: ");
for(String str:info)
System.out.print(str);
System.out.print("
");
}
}
- 每取到一条数据首先判断map中是否已有这个省的记录,若有则累加这个省的Record数据,若无则new一个Record并赋值将其put入map中。
- 每一条记录按空格拆分形成字符串数组info后下标info[0]为某某省info的最后一个则为 *人 将数字字符分离出来转换成int类型就得到数据。
- 函数的另外一个参数result是一个TreeMap<String,Record>用于存储统计数据String为省,Record为这个省的统计数据。
- 数据存储
void saveResult(Map<String,Record> result,String outLocate,String[] command,Vector<String> province,Vector<String> type){
Record totalCountryNumber = statisticTotalNumber(result);
String record;
FileOutputStream outFile = null;
try {//创建文件夹和文件
Lib.creatFile(outLocate);
outFile = new FileOutputStream(outLocate);
} catch (Exception e) {
e.printStackTrace();
}
if(province.size() == 0){// 全部省份
if(type.size() == 0){//全部省份全部类型
allProvinceAllType(totalCountryNumber,outFile);
}
else{// 全部省份部分类型
allProvincePartType(totalCountryNumber,outFile,type);
}
}
else{// 部分省份
if(type.size() == 0){//部分省份全部类型
partProvinceAllType(totalCountryNumber,province,outFile);
}
else{// 部分省份部分类型
partProvincePartType(totalCountryNumber,province,outFile,type);
}
}
try {
outFile.write(Lib.explain.getBytes());
for(String str : command)
outFile.write((str + " ").getBytes());
outFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
数据存储主要依据type和province两个选项的参数的不同而进行不同的处理,分为全部省份全部类型(即没有province和type参数)、全部省份部分类型(无province有type)以及部分省份全部类型、部分省份部分类型,这样吧不同的情况交由不同的方法处理,不至于让saveResult过于复杂和臃肿。
6、单元测试
- 获取log文件夹下文件列表
测试用例:
结果:
- 测试输出文件路径或文件不存在
测试前
测试后
程序可根据用户输入自动创建文件进行输出
- 没有-date参数
没有date参数时默认以当前时间为date参数
- 测试数据要求统计log文件里没有出现过的省份
结果:按要求此省份显示的数据应该为0
- 测试简单参数 即没有 -type 和-province选项,log文件使用example/log 下的文件
测试用例
结果与ListOut1相同
- 有-province选项无-type 选项
测试用例
结果
- 有-type无-province
默认只显示全国和log日志中出现过的省份
- -type和-province都包含
测试用例
结果
- 当输入的日期超过当前日期时(即比当前日期更大,测试时日期为2-20,此处以5-29作为测试日期)
结果显示会显示为以当前日期作为查询日期的数据
- 输入日期比最小日志时间还小时,统计数据为0,如下
7、单元测试优化和性能测试
- 测试覆盖率
性能测试:
Overview
- Memory
- CPU
LiveMemory
8、代码规范这边走
9、心路历程与收获
心路历程 :这次作业比较大的收获在于学习了github的基本使用。github久闻其名却一直没能真正地使用,这次任务算是带来了一个契机。其次比较折腾人的就是覆盖率测试和性能测试部分(Coverage无法使用直接导致重装idea,JProfiler的安装也是一波三折),还有单元测试也是第一次接触的东西。现在回头看看整个作业的经历真切地体会到了任务量的巨大(其实刚看到作业时被简直一脸懵逼 (/_),还好开始早也不至于太赶)。
体会:事先的计划和时间分配还是不够细致,直接原因是经验不足,对idea会出现的状况也是没有预料到的,问题处理的不够迅速。以后还是要经常练练手,熟悉IDE的各种功能和使用。
10、Python爬虫相关项目(Github)
这是一个Python学习的仓库,涵盖的知识足够涉及Python学习的各个方面,从Python安装到从语言基础,从线程到网络适合系统地学习Python
这个用户爬取了b站2000万用户(2016年)的信息并在知乎上展示了相关统计结果。
从爬虫是什么讲起,教你爬虫,仓库内还提供了许多实例教程,作者还给出了源码。
作者将豆瓣读书的数据爬取并保存,还开发了一个Webapp可用于对数据进行查询
爬取网易云音乐排行榜中云音乐飙升榜的歌曲以及对应的热门评论和前20最新评论。作者有博客记录开发过程中遇到的问题。