寒假作业(2/2)——疫情统计
这个作业属于哪个课程 | <2020春 W班> |
---|---|
这个作业要求在哪里 | <作文正文> |
这个作业的目标 | 开发疫情统计程序,学习github的使用 |
作业正文 | 寒假作业(2/2) |
其他参考文献 | 《构建之法》、 引导和提示 |
1.Github仓库地址、代码规范链接
2.PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 35 |
Estimate | 估计这个任务需要多少时间 | 1000 | 1645 |
Development | 开发 | 600 | 660 |
Analysis | 需求分析 (包括学习新技术) | 180 | 150 |
Design Spec | 生成设计文档 | 60 | 55 |
Design Review | 设计复审 | 30 | 15 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 20 |
Design | 具体设计 | 30 | 60 |
Coding | 具体编码 | 240 | 360 |
Code Review | 代码复审 | 30 | 30 |
Test | 测试(自我测试,修改代码,提交修改) | 120 | 120 |
Reporting | 报告 | 90 | 60 |
Test Report | 测试报告 | 30 | 40 |
Size Measurement | 计算工作量 | 20 | 10 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 60 | 30 |
合计 | 1550 | 1645 |
3.解题思路
开始的心酸历程:
一开始连 java 和javac都傻傻分不清(以前编译器一键运行,也很少用到args[]),可以说一开始的示例命令:$ java InfectStatistic list -date 2020-01-22 -log D:/log/ -out D:/output.txt 都没看懂,所以第一件事就是要去了解如何编译运行java文件,以及main中形式参数是怎样的,了解之后才知道java运行class文件,后面的参数便会依次传进args[]中。知道了我们的所有,那么就可以进行需求分析,得到我们的目的:所需。
初步构思:
处理args[]中的字符串String 便可以依照规则对-log内的日志文件处理,然后输出至-out中,而所谓的规则,就在-date ,-type,-province中,-date就是处理至这一天,-type和-province都是输出的格式,若不指定也有他们的默认输出格式 -date不指定 则应统计所有日志文件 -type不指定,则四种类型都要输出 -province不指定 则要依据日志中出现省份均需输出
从 我们所有 到 我们所需
所有:日志文本,以及一条命令行如:java InfectStatistic list -date 2020-01-22 -log D:/log/ -out D:/output.txt -type ip sp -province 福建 湖北
所需:根据命令行指示 输出文件
本质上这是一个文本的 读-处理统计-写 的问题,按格式读写不是问题,重点是在对命令行参数的解析之后根据其命令进行统计
即解析和统计
查看原图
下面给出详细的设计思路和实现过程。
4.设计实现过程
总的思路不变,和上面一开始的想法差不多:解析命令->处理文件->输出文件,那接下来局势具体的实现想法。
依据需求分析 决定分出两个类,一个CmdAnalysis类 用于解析命令;一个HandleLog类 读取统计日志文件并输出。
解析命令:
分类型:
list:
-log (必选项)
-out (必选项)
-date (可选项) 格式yyyy-mm-dd如2020-02-10
-type (可选项)ip、sp、cure、dead
-province (可选项)列举的已排序的全国+31个省份
而后依类型读取、存储相关参数 如-log D:/log/ 则将D:/log/存下备用
查看原图
处理文件:
读取log目录下所有文件名,与date比较,比其小的进行统计;
统计:进行省份 类型的统计 (可选项没有的话要用默认来统计,默认:日志中出现的省份)
查看原图
然后根据格式输出即可
5.代码说明
利用equals区分读入的参数,并判断是否输入有误,有误return false,无误return true 并分别存储,具体可看注释
boolean mustLog = false; //标记两个必选项-log -out 是否有输入
boolean mustOut = false;
if(!cmdString[0].equals("list")) {
System.out.println("命令行参数缺少list");
return false;
}
for(int i = 0;i < cmdString.length;i++) {
if(cmdString[i].equals("-log")) {
mustLog = true;
logLocation = cmdString[++i];
}
else if(cmdString[i].equals("-out")) {
mustOut = true;
outLocation = cmdString[++i];
}
else if(cmdString[i].equals("-date")) {
//检测日期合法性
if(!isCorrectDate(++i)) {
System.out.println("指定日期不合法");
return false;
}
}
else if(cmdString[i].equals("-type")) {
//检测输入类型合法性(ip\sp\cure\dead)
if(!isType(++i)) {
System.out.println("指定类型不合法");
return false;
}
}
else if(cmdString[i].equals("-province")) {
provinceShow[32] = 0; //标识含-province参数,应依据输入输出,否则含默认方式(日志中涉及省份)展示
if(!isProvince(++i)) {
System.out.println("指定省份不合法");
return false;
}
}
}
if(mustLog && mustOut) //验证必选项是否输入
return true;
else {
System.out.println("缺少输入-log 或 -out");
return false;
}
判断true时则进行存储(赋值) 否则返回false
/*
*判断指定日期是否正确
*@param 命令行位置(下标)
*@return boolean 日期符合格式 true 否则 false
*/
private boolean isCorrectDate(int i) {
if(i<cmdString.length) {
if(isValidDate(cmdString[i])) {
logDate = cmdString[i];
return true;
}else
return false;
}else
return false;
}
/*
*判断指定日期格式是否满足yyyy-MM-dd 字符串是否为数字
*@param String
*@return boole if String 满足格式 true 不满足false
*/
private boolean isValidDate(String strDate) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
try {
format.setLenient(false);
Date date = format.parse(strDate); //利用创建日期来判断格式,若抛出异常则日期格式有误
String[] sArray = strDate.split("-");
for (String s : sArray) {
boolean isNum = s.matches("[0-9]+");
if (!isNum) {
return false;
}
}
} catch (Exception e) {
//e.printStackTrace();
return false;
}
return true;
}
/*
*判断指定类型是否正确,正确则记录输出类型的种类及顺序
*@param int 在命令行的位置
*@return boolean 满足类型true 不满足false
*/
private boolean isType(int i) {
for(int a = 0;a < typeOrder.length;a++)
typeOrder[a] = -1; //-1不输出
int currentIndex = i;
if(i<cmdString.length) {
int t = 0;
for(;currentIndex < cmdString.length; currentIndex ++) {
if(cmdString[currentIndex].equals(typeString[0])) {
if(t < typeOrder.length)
typeOrder[t] = 0;
t++;
}
else if(cmdString[currentIndex].equals(typeString[1])) {
if(t < typeOrder.length)
typeOrder[t] = 1;
t++;
}
else if(cmdString[currentIndex].equals(typeString[2])) {
if(t < typeOrder.length)
typeOrder[t] = 2;
t++;
}
else if(cmdString[currentIndex].equals(typeString[3])) {
if(t < typeOrder.length)
typeOrder[t] = 3;
t++;
}
else {
break;
}
}
if(t > 0 )
return true;
else
return false;
}else
return false;
}
/*
* 判断指定省份是否正确
* @param int 在命令行的位置
* @return boolean 满足省份格式 true 不满足false
*/
private boolean isProvince(int i) {
int currentIndex = i;
if(i<cmdString.length) {
for(;currentIndex < cmdString.length && provinceShow[32] == 0; currentIndex ++) {
for(int j = 0;j < province.length;j++) {
if(cmdString[currentIndex].equals(province[j]))
provinceShow[j] = 0; //要求输出的省份
}
}
return true;
}else
return false;
}
读取日志文件,并将fileName与-date比较,筛选出符合条件的日志文件来详细统计
public void readLog() {
File file = new File(logLocation);
File[] files = file.listFiles();
for(int i = 0;i < files.length;i++) {
if(file.isDirectory()) {
String filePath = files[i].getPath();
String fileName = files[i].getName();
//System.out.println(fileName);
if(fileName.compareTo(logDate + ".log.txt") <= 0) {
//System.out.println(filePath);
statistics(filePath);
}
}
}
}
利用正则表达式详细统计
private void statistics(String filePath) {
//[\\u4E00-\\u9FA5]+ 匹配多个中文字符
String handleType[] = {"[\\u4E00-\\u9FA5]+ 新增 感染患者 \\d+人","[\\u4E00-\\u9FA5]+ 新增 疑似患者 \\d+人",
"[\\u4E00-\\u9FA5]+ 感染患者 流入 [\\u4E00-\\u9FA5]+ \\d+人","[\\u4E00-\\u9FA5]+ 疑似患者 流入 [\\u4E00-\\u9FA5]+ \\d+人",
"[\\u4E00-\\u9FA5]+ 死亡 \\d+人","[\\u4E00-\\u9FA5]+ 治愈 \\d+人","[\\u4E00-\\u9FA5]+ 疑似患者 确诊感染 \\d+人",
"[\\u4E00-\\u9FA5]+ 排除 疑似患者 \\d+人"};
BufferedReader reader = null;
try {
// 指定读取文件的编码格式,要和写入的格式一致,以免出现中文乱码
reader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "UTF-8"));
String str = null;
while ((str = reader.readLine()) != null) {
// 注释部分 "//"不处理
if(!str.startsWith("//")) {
if(str.matches(handleType[0])) {
increaseIp(str);
}
else if(str.matches(handleType[1])){
increaseSp(str);
}
else if(str.matches(handleType[2])){
ipTransfer(str);
}
else if(str.matches(handleType[3])){
spTransfer(str);
}
else if(str.matches(handleType[4])){
dead(str);
}
else if(str.matches(handleType[5])){
cure(str);
}
else if(str.matches(handleType[6])){
spDiagnose(str);
}
else if(str.matches(handleType[7])){
spExclude(str);
}
}
//System.out.println(str);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
* 对日志文件逐行处理,新增感染,全国、感染省份 感染人数增加num人
* @param 日志中的每一行
*/
private void increaseIp(String str) {
String[] strArray = str.split(" "); //以空格将一行输入分为一组字符串,便于统计
String numIp = strArray[3].substring(0, strArray[3].length()-1); //感染人数
int num = Integer.parseInt(numIp); //转换为整数便于四则运算
sum[0][0] += num; //全国感染人数增加
for(int i = 0;i < CmdAnalysis.province.length;i++) {
if(strArray[0].equals(CmdAnalysis.province[i])) { //具体到某个省感染人数增加
sum[i][0] += num;
if(provinceShow[32] == -1) { //按默认方式,标记日志中出现的省份 以默认显示,否则则按指定省份显示
provinceShow[i] = 0;
}
}
}
}
......其他函数类似
按格式输出至-out 目录
/*
* 处理符合条件的日志文件并输出
*/
public void writeLog() {
BufferedWriter fw = null;
try {
File file = new File(outLocation);
fw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, false), "UTF-8")); // 指定编码格式,以免写时中文字符异常
for(int i = 0;i < provinceShow.length-1;i++) {
if(provinceShow[i] != -1) {
fw.append(CmdAnalysis.province[i]+" ");
for(int j = 0;j<typeOrder.length;j++) {
if(typeOrder[j] != -1) {
fw.append(typeString[typeOrder[j]]+""+sum[i][typeOrder[j]]+"人 ");
}
}
fw.append("\n");
}
}
fw.append("// 该文档并非真实数据,仅供测试使用");
fw.flush(); // 全部写入缓存中的内容
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
6.单元测试截图和描述
单元测试总时间(13个测试)日志文件采用example内的3个日志文件进行验证
其中10、12、13为错误输入,则在控制台提示错误,并不输出文件
剩余为正常输入 验证结果无误
结果
查看原图
7.单元测试覆盖率优化和性能测试
单元测试覆盖率如下:
部分line未达100%,原因是代码中含异常处理情况如文件读写失败等,以及处理输入方面也有一些个别输入错误情况未涉及到,因此并未达到100%
性能优化 尽量使用正则表达式,而避免直接使用equals
8.项目路上的心路历程
由PSP表格实际耗费时间与计划时间出入甚大,虽然最终完成时间相差不大,但仔细观察,其中项计划与实际耗费时间有的多了有的少了,导致最终方差较小,实际这是对自己开发能力的不够了解或者说计划不甚合理,但谅在初次编程作业还可以理解,主要是自我感觉 编码能力有待加强,编写过程中,想法有了,编码有些许困难,比如一些字符串的正则表达式以及相关处理,以至于在开发过程中吃了不少苦头。
但这还只是编程问题,毕竟这些函数都接触过,只是时间久了没用遗忘了。最难的应该是对新事物的接受吧。这次的gitghub,单元测试等都是以前我从未接触过的,现在啃起来可以说是晦涩难懂,摸不着头脑,得一步一步地自主学习,想想自主学习正是我所缺少的,所以此次的作业也能锻炼自身的这方面的能力,收获不小,其次看自己的编码时间,说明自己的编码能力有待加强啊(但是累是真的累,快乐也是真的快乐-痛并快乐着?)
9.5个仓库
1.awesome-github-vue
简介:Vue相关开源项目库的汇总,包含UI组件、开发框架、实用库、服务端、辅助工具、应用实例、Demo示例,加强对vue的学习
2.WebBasicCommonDemos
简介:html、css、javascript、ajax、weex、vue.js、less、webpack等前端学习基础知识,巩固web基础
3.jQuery
简介:jQuery相关学习资源并包含部分案例
4.JavaScriptStudy
简介:JavaScript的学习代码总结,高级特性、数据结构、设计模式、typescript、vue、angular、react、node、webpack、weex、小程序、tensorflow…,JavaScript是世界上最好的语言!
5advanced-java
简介:互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识,后端同学必看,前端同学也可学习(利于了解后端)