github地址
https://github.com/Wangmmmm/WordCount
PSP2.1表格
PSP2.1 | PSP 阶段 | 预估耗时 (分钟) | 实际耗时 (分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 240 | 300 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 30 | 30 |
· Design Spec | · 生成设计文档 | 0 | 0 |
· Design Review | · 设计复审 (和同事审核设计文档) | 0 | 0 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 20 | 30 |
· Coding | · 具体编码 | 150 | 200 |
· Code Review | · 代码复审 | 20 | 20 |
· Test | · 测试(自我测试,修改代码,提交修改) | 5 | 50 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 5 | 5 |
· Size Measurement | · 计算工作量 | 5 | 5 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 20 |
合计 | 265 | 340 |
解题思路
每一个计数功能都是一个子类,继承一个共同的父类,并统一输入输出的接口,在每一个类内部具体实现不同功能的统计。
对输入的字符串数组有一个专门的解析器进行解析,分析出需要多少种统计子类,以及输入文件列表,是否有停用词表,输出是否新定义输出文件等功能。
代码说明
//输入解析类
class InputHandler
//把停用词表的内容转化为字符串数组加载到内存中
private void SetStopList()
//获取输出文件目录,如果没有-o参数,则设定为默认值
public String GetOutputFile()
//根据输入的参数,设置需要的统计类对象
public ArrayList<Statistics> GetStatMethods()
//将统计的结果格式化输出 filename:要读取的文件
public void BuildOutPutString(String inputfileName)
//根据输入的内容,返回一个需要读取的文件列表
public ArrayList<File> GetInputFile()
//递归调用的函数,循环查找所有文件夹下的文件
void SetAllFile(File[] files,ArrayList<File> filelist,String format)
//所有统计方法的父类,规范接口
class Statistics
//统计方法的入口 c为当前从文件读取到的一个字符
public void Stat(char c)
//获取当前统计结果
public int GetCount()
//重置统计器,可以统计多个文件,多次使用
public void Reset()
//Statistics 的子类 专门用来统计单词的数量
class WordStat
//判断字符串是否在停用词表中
boolean Ignore(String s)
//判断当前读入的字符是否是有效的
boolean TestValid(char c)
//Statistics 的子类 专门用来统计字符的数量
class CharacterStat
//Statistics的子类 专门用来统计行数 ,同时也是具体代码行,注释行,空行统计方法的父类。
class LineStat
//当前读入的行变量
protected String currentLine;
//判断当前行有效字符的位置 beginIndex:从第几个字符开始计算
protected int GetValidPos(int beginIndex)
//判断是否是只有一个字符的行
protected boolean IsOnlyContainOneChar()
//判断是否只有一个字符的注释行
protected boolean IsOneSingleCharCommentLine()
//判断是否是只有注释的行
boolean IsOnlyContainComment()
//判断呢是否是代码行的类,继承自LineStat
class CodeLineStat
//判断是否是注释行的类,继承自LineStat
class CommentLineStat
//判断是否是空行的类,继承自LineStat
class NullLineStat
重要代码
解析输入参数的类 InputHandler
package Custom; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import StatMethod.*; //输入解析类 public class InputHandler { String[] inputStrings; String defaltOutputFile="result.txt"; public InputHandler(String[] s) { inputStrings =s; SetStopList(); } //把停用词表的内容转化为字符串数组加载到内存中 private void SetStopList() { WordStat.stopList=new ArrayList<String>(); int index=StringsContain("-e"); if(index==-1)return; if(inputStrings.length<=index+1)return; String fileName=inputStrings[index+1]; WordStateIgnoreComma stater=new WordStateIgnoreComma(); File file=new File(fileName); Reader reader = null; try { // 一次读一个字符 reader = new InputStreamReader(new FileInputStream(file)); int tempchar; while ((tempchar = reader.read()) != -1) { // 对于windows下,\r\n这两个字符在一起时,表示一个换行。 // 但如果这两个字符分开显示时,会换两次行。 // 因此,屏蔽掉\r,或者屏蔽\n。否则,将会多出很多空行。 if (((char) tempchar) != '\r') { stater.Stat((char)tempchar); } } WordStat.stopList=stater.GetString(); reader.close(); } catch (Exception e) { e.printStackTrace(); } } //获取输出文件目录,如果没有-o参数,则设定为默认值 public String GetOutputFile() { int index=StringsContain("-o"); if(index==-1)return defaltOutputFile; if(inputStrings.length>(index+1)) { return inputStrings[index+1]; } return ""; } boolean statC=false; boolean statW=false; boolean statL=false; boolean statA=false; ArrayList<Statistics> stats; //根据输入的参数,设置需要的统计类对象 public ArrayList<Statistics> GetStatMethods() { stats=new ArrayList<Statistics>(); if(StringsContain("-c")!=-1) { stats.add(new CharacterStat()); statC=true; } if(StringsContain("-w")!=-1) { stats.add(new WordStat()); statW=true; } if(StringsContain("-l")!=-1) { stats.add(new LineStat()); statL=true; } if(StringsContain("-a")!=-1) { stats.add(new CodeLineStat()); stats.add(new NullLineStat()); stats.add(new CommentLineStat()); statA=true; } return stats; } public String Print(String fileName) { String s=""; int index=0; if(statC) {s+=(fileName+"," + stats.get(index).GetMethodName()+":"+stats.get(index++).GetCount()+"\r\n");} if(statW) {s+=(fileName+"," + stats.get(index).GetMethodName()+":"+stats.get(index++).GetCount()+"\r\n");} if(statL) {s+=(fileName+"," + stats.get(index).GetMethodName()+":"+stats.get(index++).GetCount()+"\r\n");} if(statA) { String methodNames="代码行/空行/注释行:"; String counts=String.valueOf(stats.get(index++).GetCount())+"/"+String.valueOf(stats.get(index++).GetCount()) +"/"+String.valueOf(stats.get(index++).GetCount()); s+=(fileName+","+methodNames+counts+"\r\n"); } return s; } String outputMessage=""; //将统计的结果格式化输出 filename:要读取的文件 public void BuildOutPutString(String inputfileName) { outputMessage+=Print(inputfileName); for(int i=0;i<stats.size();i++) { stats.get(i).Reset(); } } public String GetOutputMessage() { return outputMessage; } //根据输入的内容,返回一个需要读取的文件列表 public ArrayList<File> GetInputFile() { int i=1; for(;i<inputStrings.length;i++) { if(!IsStatCommand(inputStrings[i])) break; } String filePath=inputStrings[i]; String absoluteFileRootPath=""; if(filePath.contains(":")) { int index= filePath.lastIndexOf('\\'); absoluteFileRootPath=filePath.substring(0, index); filePath=filePath.substring(index+1); } //System.out.println(absoluteFileRootPath+'\\'+filePath); File[]fileArray ; ArrayList<File> files=new ArrayList<File>(); File rootFile; if(StringsContain("-s")!=-1) { String format=filePath.substring(filePath.indexOf('.')); if(absoluteFileRootPath=="") { rootFile=new File("./"); } else { rootFile=new File(absoluteFileRootPath); } fileArray=rootFile.listFiles(); if(fileArray.length==0)return null; SetAllFile(fileArray,files,format); } else { if(absoluteFileRootPath=="") { files.add(new File(filePath)); } else { files.add(new File(absoluteFileRootPath+'\\'+filePath)); } } return files; } //递归调用的函数,循环查找所有文件夹下的文件 void SetAllFile(File[] files,ArrayList<File> filelist,String format) { if(files.length==0)return ; for(int j=0;j<files.length;j++) { if(files[j].getName().endsWith(format)) { filelist.add(files[j]); } else if(files[j].isDirectory()) { SetAllFile(files[j].listFiles(),filelist,format); } } } int StringsContain(String s) { for(int i=0;i<inputStrings.length;i++) { if(inputStrings[i].equals(s)){return i;} } return -1; } boolean IsStatCommand(String s) { if(s.equals("-s")||s.equals("-w")||s.equals("-c")||s.equals("-l")||s.equals("-a")) { return true; } return false; } boolean IsStopListCommand(String s) { if(s.equals("-e"))return true; return false; } }
所有统计功能的的父类 Statistics
package StatMethod; public class Statistics { public int count=0; //public String methodName;
//逐字符的输入读取的内容并分析
public void Stat(char c) { }
//获取当前输入的统计结果 public int GetCount() { return count; } public String GetMethodName() { return null; }
//重置统计器 public void Reset() { count=0; } }
以统计单词数的类举例 WordStat
package StatMethod; import java.util.ArrayList; public class WordStat extends Statistics {
//输入统计的状态机 enum State{ Init, CountingChar, Skip }
//停用词表 public static ArrayList<String> stopList; State state=State.Init; String currentString=""; //具体的重置函数 public void Reset() { state=State.Init; currentString=""; super.Reset(); } public void Stat(char c) { //if(c=='\r'||c=='\n')return ; //count++; switch(state) { case Init: { if(TestValid(c)) { count++; currentString+=c; state=State.CountingChar; } break; } case CountingChar: { if(TestValid(c)) { currentString+=c; break; } else { if(Ignore(currentString)) {count--;} currentString=""; state=State.Skip; break; } } case Skip: { if(TestValid(c)) { currentString+=c; count++; state=State.CountingChar; } break; } default: break; } //System.out.println(count); } public int GetCount() { if(Ignore(currentString))count--; currentString=""; return super.GetCount(); } boolean Ignore(String s) { //System.out.println("忽略测试的字符:"+s); for(int i=0;i<stopList.size();i++) { if (stopList.get(i).equals(s)) { return true; } } return false; } boolean TestValid(char c) { if(c==' '||c=='\r'||c=='\n'||c==','||c=='\t') { return false; } else { return true; } } public String GetMethodName() { return "单词数"; } }
测试设计过程
测试过程对于每一项功能单独测试,再进行组合测试,确保覆盖了所有可执行的代码,对文件名输入文件和文件夹也作出了测试,总共设计10个测试用例:
wc.exe
wc.exe -c
wc.exe -c input.c
wc.exe -c -w input1.c -o out1.txt
wc.exe -c -w -l input2.c -o out2.txt
wc.exe -c -w -l -a input3.c -o out3.txt
wc.exe -a -l -c -w input3.c -o out4.txt
wc.exe -s -c -w -l -a *.c -o out5.txt
wc.exe -c -w -l -a input3.c -e stoplist.txt -o out6.txt
wc.exe -s -c -w -l -a *.c -e stoplist.txt -o out7.txt
测试结果见项目下bin
result.txt out1.txt out2.txt out3.txt ou4.txt out5.txt out6.txt out7.txt