这个作业属于哪个课程 | 软工 |
---|---|
这个作业要求在哪里 | 作业要求 |
这个作业的目标 | 设计、开发一个疫情统计的程序、学习对程序的优化、学习GitHub的使用、PSP(个人软件开发流程)的学习使用、《构建之法》的学习 |
作业正文 | 作业 |
其他参考文献 | Eclipse单元测试 JProfiler使用 github 博客园相关文章... |
一、GitHub仓库地址
GItHub仓库地址
二、构建之法学习成果
2.PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 45 |
Estimate | 估计这个任务需要多少时间 | 30 | 45 |
Development | 开发 | 450 | 500 |
Analysis | 需求分析 (包括学习新技术) | 30 | 15 |
Design Spec | 生成设计文档 | 30 | 25 |
Design Review | 设计复审 | 30 | 35 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 45 | 60 |
Design | 具体设计 | 30 | 50 |
Coding | 具体编码 | 300 | 450 |
Code Review | 代码复审 | 60 | 60 |
Test | 测试(自我测试,修改代码,提交修改) | 150 | 40 |
Reporting | 报告 | 60 | 135 |
Test Repor | 测试报告 | 60 | 90 |
Size Measurement | 计算工作量 | 15 | 15 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 40 |
合计 | 1350 | 1605 |
三、解题思路描述
1.所需知识点
本次任务我打算使用java语言,看到这个题目以后我想到了以下几个知识点需要被使用到
命令行参数的获取
在命令行中用户输入的参数,即list命令及其所带选项需要被程序读入并且正确处理
于是我找寻了相关的知识点如何获取java程序的命令行参数
并且编写了测试程序确保能够正确获取到命令行参数
文件的读入与输出
我们需要通过读取/log目录下的文件并且对其正确处理后将结果输出
这就涉及到java对文件输入输出的管理以及一些需要注意的细节(如编码问题)
java文件读写
2.选取正确的数据结构
仔细读取题目可以发现可以将每一个省定义为一个对象,即可定义Province类,该类拥有如下数据成员
/*记录一个省的各项数据*/
class Province{
String name; /*省名*/
int infect; /*感染人数*/
int seeming; /*疑似人数*/
int dead; /*死亡人数*/
int cured; /*治愈人数*/
public Province() {
infect = seeming = dead = cured = 0;
}
}
3.定义文件结构
221701126(目录名为学号)
\--src
\--InfectStatistic.java
\--Lib.java
\--README.md
描述你的项目,包括如何运行、功能简介、作业链接、博客链接等
\--codestyle.md
描述你之前定的代码风格
4.代码风格制定
编写代码前肯定需要规范自己的代码风格,这关系编程过程中的代码样式,故参考了所给的阿里巴巴的java代码风格推荐,制定了属于自己的代码风格
5.需求分析
能够统计统计文件内信息并打印出需要了解的内容到目标文件中
1.根据命令行处理参数
2.选出目标目录下合适的文件
3.对每个文件的每行字符串进行处理,并记录数据
4.输出要求的行到目标文件
6.文件中字符串类型
分为以下几种:
<省> 新增 感染患者 n人
<省> 新增 疑似患者 n人
<省1> 感染患者 流入 <省2> n人
<省1> 疑似患者 流入 <省2> n人
<省> 死亡 n人
<省> 治愈 n人
<省> 疑似患者 确诊感染 n人
<省> 排除 疑似患者 n人
7.处理文件
看到文件结构就可以想到应该逐行处理,那么需要将字符串以空格分割成数组,更方便。接着可以知道第一个字符串是省份,可以存起来,最后应该字符串是人数,可以取出,中间的字符串就是行为,我们可以一个一个判断,遇到可以直接判断的字符串,就直接进行处理,不然就判断下一个字符串,再综合判断其行为,这样整个逻辑的判断就十分清晰了。
8.输出文件
先将所有省份按首字母顺序存放到一个数组,接着遍历数组,对每个省份进行判断是否要输出,如若需要输出,再将其作为参数传入相应的方法,再做具体判断。接着该方法中会进行type province选项参数的判定。
四、设计实现过程
代码流程
具体开发过程(逐步实现)
五、代码说明
1.数据结构
数据结构采用Province,封装了省名以及各项指标数据,如感染人数、疑似患者等,使用其即可表示单独一个省的情况。
class Province{
String name; /*省名*/
int infect; /*感染人数*/
int seeming; /*疑似人数*/
int dead; /*死亡人数*/
int cured; /*治愈人数*/
public Province() {
infect = seeming = dead = cured = 0;
}
}
2.处理命令行参数
获取args的参数,对args数组的数据进行处理
如果是-log命令,则存储输入路径
如果是-out命令,则存储输出路径
如果是-date命令,则存储日期
如果是-type命令,则存储type选项
如果是-province命令,则存储province选项
public static void solveArgs(String[] args) {
int i = 0;
int pos = 1;
while(pos < args.length) {
String arg = args[pos];
// System.out.println(pos + "-" + arg);
if(arg.indexOf('-') == 0) {//这是命令
if(arg.equals("-log")) {//处理输入路径
inputPath = args[pos + 1] + "\\";
pos+=2;
}
else if(arg.equals("-out")) {//处理输出路径
outputPath = args[pos + 1] + "\\";
pos+=2;
}
else if(arg.equals("-date")) {//处理日期
targetDate = args[pos + 1];
pos+=2;
}
else if(arg.equals("-type")) {
for(i = pos + 1; i < args.length; i++) {
String param = args[i];
if(param.indexOf('-') != 0) {//这是参数
typeItem.add(param);
}
else {
pos = i;
break;
}
}
}
else if(arg.equals("-province")) {//处理province命令
for(i = pos + 1; i < args.length; i++) {
String param = args[i];
if(param.indexOf('-') != 0) {//这是参数
provinceItem.add(param);
}
else {
pos = i;
break;
}
}
}
if(i == args.length) {
break;
}
}
}
}
以下展示用于存储各项数据的数据成员
public static String inputPath = "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\log\\";
public static String outputPath = "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\result\\out.txt";
public static String targetDate = "";
public static ArrayList<String> typeItem = new ArrayList<String>();/*记录type命令所带的选项*/
public static ArrayList<String> provinceItem = new ArrayList<String>();/*记录province命令所带的选项*/
3.寻找需要处理的文件
用一个vector来存储需要处理的文件,对目录下每一文件进行判断,若其日期小于命令行获取的date参数,则进入vector中存储
比较算法则是采用字符串的compareTo,即将文件名转化成字符串,接着进行判断
最后再放入“全国”
public static void solveDateOrder(String targetDate) {
String maxDate = getMaxDate();
if(targetDate.compareTo(maxDate) > 0) {
System.out.println("日期超出范围");
return;
}
//获取输入路径下的所有文件
File file = new File(inputPath);
if(file.isDirectory()) {
Vector<String> toHandleDate = new Vector<String>();//获取符合要求待处理的日期文件
String[] fileNames = file.list(); // 获得目录下的所有文件的文件名
for(String fileName : fileNames) {
fileName = fileName.substring(0, fileName.indexOf('.'));//截断后缀名
//日期比较
if(fileName.compareTo(targetDate) <= 0) {
toHandleDate.add(fileName);
System.out.println(fileName);
}
else {
break;
}
//System.out.println(fileName);
}
if(toHandleDate.size() > 0) {
solveEveryFile(toHandleDate);
}
map.put("全国", country);
}
}
4.处理每个文件
对每个文件都逐行获取,接着使用字符串的split函数对其按空格进行分割,即可得到information数组
String[] information = str.split("\\s+");
关键算法:判断第二个字符串是什么
若是新增,则判断第三个字符串是感染患者还是疑似患者,进行数据统计
switch (information[1]) {
case "新增":
if(information[2].equals("感染患者")) {//感染患者的情况
p.infect += number;
country.infect += number;
//System.out.println(num);
}
else {//疑似患者的情况
p.seeming += number;
country.seeming += number;
}
break;
若是“感染患者”,则断定为感染者流入情况,则取出流入的省份已经人数进行数据处理
case "感染患者":
String p2 = information[3];//取出流入的省份名称
if(map.get(p2) != null) {//若该省份已经出现过
Province anotherProvince = map.get(p2);
anotherProvince.infect += number;
p.infect -= number;
}
else {
Province province2 = new Province();
province2.name = p2;
province2.infect += number;
p.infect -= number;
map.put(p2, province2);
}
break;
若是“疑似患者”,则判断是确诊还是流入,若为确诊,则减少疑似人数,增加感染人数,若为流入,如上处理
case "疑似患者":
//判断是流入还是确诊
if(information[2].equals("流入")) {
String p3 = information[3];//取出流入的省份名称
if(map.get(p3) != null) {//若该省份已经出现过
Province anotherProvince = map.get(p3);
anotherProvince.seeming += number;
p.seeming -= number;
}
else {
Province province3 = new Province();
province3.name = p3;
province3.infect += number;
p.infect -= number;
map.put(p3, province3);
}
}
else {//确诊
p.infect += number;
p.seeming -= number;
country.infect += number;
country.seeming -= number;
}
break;
若为“死亡”,则减少感染人数,增加死亡人数
case "死亡":
p.infect -= number;
p.dead += number;
country.infect -= number;
country.dead += number;
break;
若为“治愈”,则减少感染人数,增加治愈人数
case "治愈":
p.infect -= number;
p.cured += number;
country.infect -= number;
country.cured += number;
break;
若为“排除”,则减少疑似人数
case "排除":
p.seeming -= number;
country.seeming -= number;
break;
5.获取各行人数
通过substring来截取到“人”的下标之前的子串
public static int getNumber(String[] information) {
//获取人数
String numString = information[information.length - 1];
int index = numString.indexOf("人");
numString = numString.substring(0, index);
int number = Integer.parseInt(numString);
return number;
}
6.打印结果
事先按顺序存储所有的省份
public static String[] province = {"全国", "安徽", "北京", "重庆", "福建", "甘肃", "广东", "广西", "贵州", "海南", "河北",
"河南", "黑龙江", "湖北", "湖南", "吉林", "江苏", "江西", "辽宁", "内蒙古", "宁夏", "青海", "山东", "山西", "陕西",
"上海", "四川", "天津", "西藏", "新疆", "云南", "浙江"
};
通过for循环来判断每一个省,看看是否需要输出
public static void printResult() {
try {
File output = new File(outputPath);
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(output));
if(provinceItem.size() == 0) {
for(String provinceName : province) {
if(map.get(provinceName) != null) {
printTheProvince(provinceName, osw);
}
}
}
else {
for(String provinceName : province) {
if(provinceItem.contains(provinceName)) {
printTheProvince(provinceName, osw);
}
}
}
osw.flush();
osw.close();
} catch (IOException e) {
}
}