WordCount
1.Github项目地址
https://github.com/chaindreamjet/WordCount
2.PSP表格
PSP2.1 |
PSP阶段 |
预估耗时 (分钟) |
实际耗时 (分钟) |
Planning |
计划 |
20 |
30 |
· Estimate |
· 估计这个任务需要多少时间 |
20 |
30 |
Development |
开发 |
700 |
940 |
· Analysis |
· 需求分析 (包括学习新技术) |
30 |
40 |
· Design Spec |
· 生成设计文档 |
0 |
0 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
0 |
0 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
20 |
30 |
· Design |
· 具体设计 |
50 |
60 |
· Coding |
· 具体编码 |
420 |
600 |
· Code Review |
· 代码复审 |
60 |
90 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
120 |
120 |
Reporting |
报告 |
50 |
70 |
· Test Report |
· 测试报告 |
30 |
40 |
· Size Measurement |
· 计算工作量 |
10 |
15 |
· Postmortem & Process Improvement Plan |
· 事后总2112结, 并提出过程改进计划 |
10 |
15 |
|
合计 |
770 |
1040 |
3.解题思路
需求是有6种:
-c 统计字符数。这个很简单,用Reader每读一个字符计一个数。
-l 统计行数。这个也简单,用BufferReader读一行计一个数。
-w 统计单词数。 这个稍微麻烦一点。需求中说用空格、制表符、换行、逗号(下文称为分隔符)隔开的算单词。我一开始的想法是统计分隔符的个数,根据这个个数得到单词数。不过要解决以下三个问题:a,相邻单词用多个分隔符隔开。这个问题用循环读加判断可以解决。b,开头就是分隔符。设置一个循环控制变量可以解决。c,结尾有分隔符。有分隔符可以直接计单词数,无分隔符的时候加一个单词数。
-a 统计代码行、注释行、空行数。这个有些麻烦,因为对于代码行、注释行、空行的定义比较繁琐。基本想法是逐字符读取,用一个变量记录'{' '}'个数,如果读到"//" 或 "/*",则根据花括号个数判断是注释行还是代码行,然后读下一行;如果读到"*/",则通过判断"*/"是否位于行末或者其后只有一个'{'或'}',来判断是注释行还是代码行,然后读下一行;如果读到行末了,则根据有效字符个数来判断属于空行还是代码行;如果都没读到上述字符以及空格,则判断其属于代码行。
-e 后面接停用词表,统计单词数但不包括停用词表中的词。看到这个,有点像词法分析器的感觉...不过也没那么复杂,只需把读到的单词拿去比较就行。在 -w 功能的基础上,增加一个字符串变量用来存储当前读到的单词,当读到分隔符的时候,就把这个字符串拿去和停用词表中的单词逐个比较,如果有,单词数不加,如果没有,单词数则加。虽然增加的功能不难实现,但是繁琐的在于把停用词表中的单词提取出来存放到一个字符数组中,相当于也要读这个文件的单词,于是代码量就是 -w 功能的两倍多了。
-s 处理当前目录下所有符合条件的文件。控制台会把*.c展开成当前目录下的所有c文件,所以不需要写额外函数,只需把所有文件路径保存下来逐一处理即可。
-o 指定输出到文件。就把上述数个函数的返回值设为字符串,格式类似于“test.c, 单词数:10”,然后把用户的输入(指定的功能)造成的所有函数返回值添加到一个字符串里,再用Writer输出到文件。
需求中还有一项要求说要按字符数,单词数,行数的顺序输出;并且有时要处理多个文件,还有停用词表。所以主函数的想法是先扫描一遍用户的输入,把要统计的东西用一个boolean数组记下来,每个元素表示是否要某项功能,以及记下目录、输入文件、停用词表文件、输出文件等信息。然后,再逐一处理这些文件,这样就能够做到满足这些需求。
4.程序设计实现
没有采用面向对象设计的方法,没有很大必要吧。直接写几个static的函数就行了。命令行的输入参数传到main函数的String args[]参数里,可以很轻松的直接引用args这个字符数组。JAVA MAIN函数的参数说明见这里【1】
-c -l 。没什么值得说的。
-w 统计单词数。正如上文所说,通过计文件中分隔符的数来得到单词数。要解决的三个问题:a,相邻单词中间有多个分隔符的情况。添加一个word整形变量,1表示“有单词”,0表示“非单词”。每次开始读一个新单词时,word置零;即当读到分隔符时,word置1,表示分隔符前面有个单词;然后循环读,直到读到下一个非分隔符,跳出循环。最后让单词数加上word的值,继续外层循环。b,开头有分隔符。外层循环设计一个循环控制变量 i 初值为1(在循环中计数,但实际上对控制循环无用,只对这个问题有用),当读到分隔符时,首先判断这个循环控制变量 i 是否等于1,如果等于,则说明此分隔符前面没有单词,word置0;如果不为1,则按平常步骤处理。c,结尾有无分隔符。如果结尾有分隔符,则无需再做任何事情(因为读到分隔符会加单词数);如果结尾没有分隔符,则加一步,就让单词数加一。
-a 统计代码行、注释行、空行数。添加一个uselessChar变量记录'{' '}'个数。如果此行长度小于1,则为空行;否则循环读取字符。如果读到'{' '}'则uselessChar自增1;如果读到"//"或"/*",再通过判断uselessChar的个数来判断此行属于注释行还是代码行,然后读下一行;如果读到"*/"则判断其是否位于行末或者其后只有一个'{'或'}',来判断是注释行还是代码行,然后读下一行;如果读到的不是空格,则此行为代码行;如果读到行末,再通过判断uselessChar的个数来判断此行属于空行还是代码行。
-e 停用词表。需求中提到停用词表中可有多个单词,每个单词用空格隔开。那么在这个函数的第一个部分,从停用词表中提取单词并保存到一个动态字符数组stopList,用一个字符串word保存当前读到的单词。逐字符读取到word中,当读到空格时,循环读掉空格,然后把word添加到stopList中;循环此操作。在这个函数的第二个部分,同样也是用一个字符串word保存当前读到的单词,读到分隔符时,循环读掉分隔符,然后把word去和stopList中的停用单词逐一比较,如果无则计数,有则不计数。
-o 指定输出到文件。就把上述数个函数的返回值设为字符串,格式类似于“test.c, 单词数:10”,然后把用户的输入(指定的功能)造成的所有函数返回值添加到一个字符串里,再用Writer输出到文件。如果没有指定输出文件,则默认输出到result.txt。
main函数中有一个关键点:boolean数组 func[],元素个数7个,表示是否实现某项功能的数组。0:-c 1:-w 2:-l 3:-a 4:-e 5:-s 6 -o
5.代码说明
展示-w -a -e 这几个稍复杂一点函数说明,注释比较清楚。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // -w 2 public static String fileWords(String fileName) { 3 File file = new File(fileName); 4 String str = ""; 5 int count = 0;//统计单词数 6 int word; //是否有单词 7 Reader reader = null; 8 try { 9 // 一次读一个字符 10 reader = new InputStreamReader(new FileInputStream(file)); 11 int tempchar; 12 // i 在循环中计数,但实际上对控制循环无用。用来应对开头就是分隔符的情况 13 for (int i = 1;(tempchar = reader.read()) != -1;i++) { 14 //每次读新词,word置0 15 word = 0; 16 while (((char) tempchar) == ' ' || ((char)tempchar) == ',' || ((char)tempchar) == '\r' ||((char)tempchar) == '\n') { 17 //如果i为1则说明是开头的分隔符,直接跳过 18 if(i==1) { 19 break; 20 } 21 else { 22 word = 1; 23 if((tempchar = reader.read()) != -1) { 24 continue; 25 } 26 //如果读到文件末尾了,即分隔符在文件末尾,则不需要做任何事情。不过由于这个外循环最后一步是count自增,所以我这里先减。到最后count再自增就行 27 else { 28 count--; 29 } 30 } 31 } 32 count += word; 33 } 34 str = fileName + ", 单词数:"+ ++count +"\r\n"; 35 reader.close(); 36 } catch (Exception e) { 37 e.printStackTrace(); 38 } 39 finally { 40 if (reader != null) { 41 try { 42 reader.close(); 43 } catch (IOException e1) { 44 } 45 } 46 } 47 return str; 48 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // -a 2 public static String fileMoreLines(String fileName) { 3 File file = new File(fileName); 4 String str = ""; 5 BufferedReader reader = null; 6 try { 7 reader = new BufferedReader(new FileReader(file)); 8 String tempString = null;//当前读到的行 9 int codeLine = 0; 10 int blankLine = 0; 11 int noteLine = 0; 12 // 一次读入一行,直到读入null为文件结束 13 while ((tempString = reader.readLine()) != null) { 14 //读到的字符串长度小于1,为空行 15 if(tempString.length()<1) 16 { 17 blankLine++; 18 continue; 19 } 20 int uselessChar = 0;//'{' '}' 的个数 21 //逐字符读 22 for(int i = 0;i<tempString.length();i++){ 23 //读到 '{' '}'则uselessChar++ 24 if(tempString.charAt(i)=='{' || tempString.charAt(i)=='}'){ 25 uselessChar++; 26 } 27 //读到注释符 28 else if(tempString.charAt(i)=='/' && (tempString.charAt(i+1)=='/'||tempString.charAt(i+1)=='*')){ 29 //如果 '{' '}'个数小于2,则为注释行 30 if(uselessChar<2){ 31 noteLine++; 32 break; 33 } 34 //2以上为代码行 35 else { 36 codeLine++; 37 break; 38 } 39 } 40 //读到注释右符 41 else if(tempString.charAt(i)=='*' && tempString.charAt(i+1)=='/') { 42 //位于行末,注释行 43 if(i==(tempString.length()-2)) { 44 noteLine++; 45 break; 46 } 47 //位于行末-1的位置且行末字符为'{' '}',注释行 48 else if(i==(tempString.length()-3) && (tempString.charAt(i+2)=='{' || tempString.charAt(i+2)=='}')) { 49 noteLine++; 50 break; 51 } 52 //否则代码行 53 else { 54 codeLine++; 55 break; 56 } 57 } 58 //如果都没读到上述字符,且不为空格,那么为有效字符,为代码行。 59 else if(tempString.charAt(i)!=' '){ 60 codeLine++; 61 break; 62 } 63 //如果读到行末了 64 if((i==tempString.length()-1)){ 65 // '{' '}'个数小于2,为空行 66 if(uselessChar<2) { 67 blankLine++; 68 } 69 //2以上,代码行 70 else { 71 codeLine++; 72 } 73 } 74 } 75 } 76 str = fileName + ", 代码行/空行/注释行 :" + codeLine +"/" + blankLine +"/" + noteLine +"\r\n"; 77 reader.close(); 78 } catch (IOException e) { 79 e.printStackTrace(); 80 } finally { 81 if (reader != null) { 82 try { 83 reader.close(); 84 } catch (IOException e1) { 85 } 86 } 87 } 88 return str; 89 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //-e 2 public static String stopWords(String infile, String stopfile){ 3 /* 4 * 先把stopfile指定的文件的单词读到stopList里 5 */ 6 String all = "";//在读停用词表时,先把整个文件读到这个字符串里 7 String word = "";//读停用词表时,用来存当前读到的单词 8 List<String> stopList = new ArrayList<>(); 9 BufferedReader reader = null; 10 try { 11 reader = new BufferedReader(new FileReader(stopfile)); 12 String tempString = null; 13 // 一次读入一行,直到读入null为文件结束,读到all字符串里 14 while ((tempString = reader.readLine()) != null) { 15 all = all + " " + tempString; 16 } 17 reader.close(); 18 } catch (IOException e) { 19 e.printStackTrace(); 20 } finally { 21 if (reader != null) { 22 try { 23 reader.close(); 24 } catch (IOException e1) { 25 } 26 } 27 } 28 29 //开始逐字符循环读停用词表 30 for(int i = 0;i<all.length();i++) { 31 //如果读到有效字符则加到word中 32 if(all.charAt(i)!=' ' && all.charAt(i)!='\r' && all.charAt(i)!='\n' && all.charAt(i)!='\t') { 33 word += all.charAt(i); 34 } 35 else { 36 if(word != "") { 37 stopList.add(word); 38 word = ""; 39 } 40 } 41 } 42 //将word添加到stopList中 43 stopList.add(word); 44 45 /* 46 * 再读infile指定的文件的单词,与stopList进行对比。 47 */ 48 File file = new File(infile); 49 String str = ""; 50 word = ""; 51 int hasword;//有无单词,0无,1有 52 int count = 0;//统计单词数 53 BufferedReader buttferedReader = null; 54 try { 55 buttferedReader = new BufferedReader(new FileReader(file)); 56 String tempString = null; 57 String whole = buttferedReader.readLine();//把整个文件读到whole字符串里,对whole进行操作 58 // 一次读入一行,直到读入null为文件结束 59 while ((tempString = buttferedReader.readLine()) != null) { 60 whole = whole +'\n'+ tempString; 61 } 62 for(int i = 0;i<whole.length();i++) { 63 hasword = 0; 64 //读到有效字符,添加到word中 65 if(whole.charAt(i)!=' ' && whole.charAt(i)!='\r' && whole.charAt(i)!='\n' && whole.charAt(i)!='\t' && whole.charAt(i)!=',') { 66 word += whole.charAt(i); 67 } 68 //读到分隔符 69 else { 70 //循环读 71 while(whole.charAt(i)==' ' || whole.charAt(i)=='\r' || whole.charAt(i)=='\n' || whole.charAt(i)=='\t' || whole.charAt(i)==',') { 72 if(!word.isEmpty()) { 73 //如果word不为空则把word拿去和stopList比较 74 hasword = 1; 75 for(int j = 0;j<stopList.size();j++) { 76 if(word.equals(stopList.get(j))) { 77 hasword = 0; 78 } 79 } 80 //比完把word置空 81 word = ""; 82 } 83 i++; 84 } 85 //和读单词数中同样的道理 86 if(whole.charAt(i)!=' ' && whole.charAt(i)!='\r' && whole.charAt(i)!='\n' && whole.charAt(i)!='\t' && whole.charAt(i)!=',') { 87 i--; 88 } 89 } 90 count += hasword; 91 } 92 str = infile + ", 单词数:" + ++count + "\r\n"; 93 buttferedReader.close(); 94 } catch (IOException e) { 95 e.printStackTrace(); 96 } finally { 97 if (buttferedReader != null) { 98 try { 99 buttferedReader.close(); 100 } catch (IOException e1) { 101 } 102 } 103 } 104 return str; 105 }
6.测试设计过程
采用白盒测试的方法。对每个功能模块先进行单独测试,然后集中测试。
-c 统计字符数。
测试用例。
路径 | 测试用例 | 预期输出 | 实际输出 |
1-3 | 空 | 0 | 0 |
1-2-3 | a | 1 | 1 |
-l 统计行数。
设计两个测试用例
路径 | 测试用例 | 预期输出 | 实际输出 |
1-3 | 空 | 0 | 0 |
1-2-3 | ! | 1 | 1 |
-w 统计单词数。
测试用例
测试用例 | 预期输出 | 实际输出 |
空 | 0 | 0 |
c | 1 | 1 |
, | 0 | 0 |
a\t | 1 | 1 |
b a | 2 | 2 |
-a 统计代码行/注释行/空行数
测试用例
测试用例 | 预期输出(代码行/空行/注释行) | 实际输出 |
空 | 0,0,0 | 0,0,0 |
\na | 1,1,0 | 1,1,0 |
a | 1,0,0 | 1,0,0 |
{ | 0,1,0 | 0,1,0 |
// | 0,0,1 | 0,0,1 |
}// | 0,0,1 | 0,0,1 |
*/co | 1,0,0 | 1,0,0 |
a*/ | 0,0,1 | 0,0,1 |
*/{ | 0,0,1 | 0,0,1 |
{{// | 1,0,0 | 1,0,0 |
-e 停用词表
测试用例
测试用例(停用词表) | 测试用例(源文件) | 预期输出 | 实际输出 |
空 | 空 | 0 | 0 |
空 | a | 1 | 1 |
a | a | 0 | 0 |
a | a b | 1 | 1 |
a | ,a | 0 | 0 |
a | c | 1 | 1 |
a | b c | 2 | 2 |
a | ,b a | 1 | 1 |
a b | a | 0 | 0 |
a b | a b | 0 | 0 |
a b | ,bc | 1 | 1 |
a bc | bc a | 1 | 1 |
-s 目录下所有符合条件的文件
测试用例
已知默认目录下有wc.c test/wc2.c 两个c文件
测试用例(扩展名) | 功能 | 预期输出 | 实际输出 |
*.c char.c(a b ab,q,d) wc.c(a) |
-w | 5,1 |
5,1 |
*.c wc.c(a) |
-w | 1 | 1 |
*.txt stop.txt(out =) |
-c | 5 | 5 |
-o 输出到文件
测试用例
测试用例 | 文件 | 功能 | 预期输出 | 实际输出 |
空(不指定输出文件) |
wc.c(a) | -w | 1 |
result.txt(1) |
out.txt |
wc.c(a) | -l | 1 | out.txt(1) |
main函数
测试用例
wc.c:
int main()
{
printf("hello world!);
return 0;
}
char.c:
int a = 0;
char out
stop.txt:
return } out =
测试用例 | 预期输出 | 实际输出 |
-a -w -l -c wc.c |
wc.c, 字符数:49 |
result.txt: wc.c, 字符数:49 |
-w -c wc.c -o out.txt |
wc.c, 字符数:49 |
out.txt: wc.c, 字符数:49 |
-s -w -c *.c -o out.txt |
char.c, 字符数:20 |
out.txt: char.c, 字符数:20 |
-s -w -l *.c -e stop.txt -o out.txt |
char.c, 单词数:4 |
out.txt: char.c, 单词数:4 |
最后的打包方法参考【2】
7.参考文献链接
1.http://blog.csdn.net/cherrybomb1111/article/details/71730114