https://github.com/HuangDongPeng/WordCount.git
1.1 PSP
PSP2.1 |
PSP阶段 |
预估耗时 (分钟) |
实际耗时 (分钟) |
Planning |
计划 |
30 |
30 |
· Estimate |
· 估计这个任务需要多少时间 |
5h |
12h |
Development |
开发 |
2h |
4h |
· Analysis |
· 需求分析 (包括学习新技术) |
30min |
30min |
· Design Spec |
· 生成设计文档 |
- |
- |
· Design Review |
· 设计复审 (和同事审核设计文档) |
- |
- |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
5min |
5min |
· Design |
· 具体设计 |
10 |
0 |
· Coding |
· 具体编码 |
2h |
3h |
· Code Review |
· 代码复审 |
10 |
10 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
2h |
3h |
Reporting |
报告 |
1h |
1h |
· Test Report |
· 测试报告 |
|
|
· Size Measurement |
· 计算工作量 |
|
|
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
20min |
30min |
|
合计 |
8h |
12h |
2.1 WordCount需求说明
WordCount的需求可以概括为:对程序设计语言源文件统计字符数、单词数、行数,统计结果以指定格式输出到默认文件中,以及其他扩展功能,并能够快速地处理多个文件。
wc.exe -c file.c //返回文件 file.c 的字符数
wc.exe -w file.c //返回文件 file.c 的单词总数
wc.exe -l file.c //返回文件 file.c 的总行数
wc.exe -o outputFile.txt //将结果输出到指定文件outputFile.txt
wc.exe -s //递归处理目录下符合条件的文件
wc.exe -a file.c //返回更复杂的数据(代码行 / 空行 / 注释行)
wc.exe -e stopList.txt // 停用词表,统计文件单词总数时,不统计该表中的单
2.2 解题思路及分析过程
功能实现
1.单词统计
按字符流读取文件,对每一个字符做判断,如果不是换行符或空格则继续往下读取;当读取到换行符或者空格是,将前面读到的字符拼作一个单词
,单词计数加一:
while ((tempChar = reader.read()) != -1) { if ((tempChar >= 65 && tempChar <= 90) || (tempChar >= 97 && tempChar <= 122)) { isChar = true; } else { if (isChar) { isChar = false; wordCount++; } continue; } }
2.字符计数
每读入一个字符,判断是不是回车换行符,不是则字符计数器加一:
while ((tempChar = reader.read()) != -1) { //判断是不是回车 if (!(tempChar == 13 || tempChar == 10||tempChar==9)) charCount++; character = (char) tempChar; } reader.close(); }
3.行数读取
调用java API,统计计数
reader = new BufferedReader(new FileReader(file)); String tempString = null; int line = 0; while ((tempString = reader.readLine()) != null) { line++; } reader.close();return line;
4.递归获取文件
获取文件目录,判断是不是目录,如果是目录则递归获取该目录下的内容;如果符合要求的文件,则先将文件名存储,与之后的执行一同进行
File tmpFile = new File(dir); if (tmpFile.isDirectory()) { try { String[] fileNames = tmpFile.list(); if (fileNames.length != 0) { for (String s : fileNames) { String newPath = dir + "/" + s; FindFile(newPath); } } } catch (Exception e) { e.printStackTrace(); } } else { if (dir.contains(fomatName)) { int filePointIndex=dir.lastIndexOf("."); String rightFormatName=dir.substring(filePointIndex); if(rightFormatName.equals(fomatName)) canBeFoundFile.add(dir);//存储符合后缀文件 } }
5.获取不同类型的行数
获取代码行:非注释、非空的都是代码行
获取注释行:包含//或者包囊在 /* ..*/中的都是注释行,按字符读取,如果读取到/*则进入注释统计模式,直到读取到*/才退出该模式继续统计
获取空行:不解释
boolean isNote=false; reader = new BufferedReader(new FileReader(file)); String tempString = null; int emptyLine = 0; int codeLine = 0; int noteLine = 0; while ((tempString = reader.readLine()) != null) { if(tempString.contains("/*")){ isNote=true; } else if(tempString.contains("*/")) { isNote=false; } if (tempString.contains("//")||tempString.contains("*/")||isNote) noteLine++; else if (tempString.isEmpty() || IsEmpty(tempString)) { emptyLine++; } else codeLine++;
6.停用表功能
若存在于停用表中的单词则统计单词数时不计入统计。
先获取停用表中的单词,存储与List,然后读取指定文件单词做判断,若属于停用表中的则单词计数器不增加。
//读取停用表内容
while ((tempChar = reader.read()) != -1) { if ((tempChar >= 65 && tempChar <= 90) || (tempChar >= 97 && tempChar <= 122)) { isChar = true; sb.append((char) tempChar); } else { if (isChar) { wordTable.add(sb.toString()); sb = new StringBuilder(); isChar = false; } continue; } } if (isChar && sb.length() != 0) { wordTable.add(sb.toString()); }//读取文件内容while ((tempChar = reader.read()) != -1) { if ((tempChar >= 65 && tempChar <= 90) || (tempChar >= 97 && tempChar <= 122)) { isChar = true; localSb.append((char) tempChar); } else { if (isChar) { if (!IsInTable(wordTable, localSb.toString())) { wordCount++; } localSb = new StringBuilder(); isChar = false; } continue; }
7.输出文件
修改输出文件名称,不解释
参数分析
在知道执行哪些命令前需要遍历一遍输入参数,存储要执行的指令,在统一结合递归获取文件等操作执行指令
//首先遍历一边参数,找出需要执行的指令
for (int i = 0; i < inputArgs.length; i++) {
//如果使用了停用表指令,就获取停用表名
if (inputArgs[i].contains("-e")) { isUseStopList = true; i++; stopListFileName = inputArgs[i]; } //是否需要重新定义输出文件名并获取输出文件名 if (inputArgs[i].contains("-o")) { isOutPutFile = true; i++; outputFileName = inputArgs[i]; }
//是否递归搜索 if (inputArgs[i].contains("-s")) { isGetDirFiles = true; } }
//寻找目标文件,目标文件之前的都是执行参数
for (int i = 0; i < inputArgs.length; i++) { if (inputArgs[i].contains(".")) { fileNameIndex = i; filePath = inputArgs[i]; if (filePath.contains(".")) { int pointIndex = filePath.lastIndexOf("."); fomatName = filePath.substring(pointIndex); } break; } } if (!isGetDirFiles) { for (int i = 0; i < fileNameIndex; i++) { OrderJudge(inputArgs[i]); } } else { SetFileDir(inputArgs); FindFile(fileDir); for (String s : canBeFoundFile) { filePath = s; System.out.println(s); for (int i = 0; i < fileNameIndex; i++) { OrderJudge(inputArgs[i]); } } } //输出文件 OutPutFile(outputFileName, sb); }
2.3测试
编写测试,覆盖每一个函数及功能,每一个的形参包括:读取的文件名,预期的输入;
每一个调用的输出结果是:
测试名称、预期结果、测试输出结果、是否符合预期结果;
public boolean Test_CharCount(int expResult, String path) { return PrintErrorDetail(GetMethodName(), expResult, Main.ReadChar(path)); } public boolean Test_WordCount(int expResult, String path) { return PrintErrorDetail(GetMethodName(), expResult, Main.ReadWord(path)); } public boolean Test_LineCount(int expResult, String path) { return PrintErrorDetail(GetMethodName(), expResult, Main.ReadLine(path)); } public boolean Test_ReadDiffLine(int emptyLine, int noteLine, int codeLine, String path) { int[] expResult = new int[3]; expResult[0] = codeLine; expResult[1] = emptyLine; expResult[2] = noteLine; String[] lineNames = {"codeLine", "emptyLine", "noteLine"}; int[] testResult = Main.GetDifferentLine(path); for (int i = 0; i < 3; i++) { PrintErrorDetail(GetMethodName() + " " + lineNames[i], expResult[i], testResult[i]); if (expResult[i] != testResult[i]) return false; } return true; } public boolean Test_StopList(int expResult, String stoplistPath, String readFilePath) { int testResult = Main.StopWordTable(stoplistPath, readFilePath); return PrintErrorDetail(GetMethodName(), expResult, testResult); } public boolean Test_Recursion(int expFileCount, String path, String formateName) { Main.fomatName = formateName; return PrintErrorDetail(GetMethodName(), expFileCount, Main.FindFile(path)); } public boolean Test_OutputFile(StringBuilder sb, String outputPath) { System.out.println("test: " + GetMethodName()); if (Main.OutPutFile(outputPath, sb)) { System.out.println("out put file success"); return true; } System.out.println("out put file failed"); return false; } public boolean Test_Recursion_StopList(String formatName, String stopListName, int[] expCount) { Main.fomatName = formatName; Main.FindFile("./"); boolean result = true; for (int i = 0; i < Main.canBeFoundFile.size(); i++) { if (!Test_StopList(expCount[i], stopListName, Main.canBeFoundFile.get(i))) { result = false; } } return result; } public boolean Test_Recusion_WordRead_OutputFile(String formatName, String outputFileName, int[] expResult) { Main.fomatName = formatName; Main.FindFile(Main.fileDir); boolean result = true; for (int i = 0; i < Main.canBeFoundFile.size(); i++) { if (expResult[i] != Main.ReadWord(Main.canBeFoundFile.get(i))) { result = false; } } if (result) { result = Main.OutPutFile(outputFileName, Main.sb); } return result; }