第二周个人作业:WordCount
一、Github项目地址:https://github.com/aier02/wordcount
二、PSP表格
PSP2.1 |
PSP阶段 |
预计耗时 (分钟) |
实际耗时 (分钟) |
Planning |
计划 |
20 |
15 |
· Estimate |
· 估计这个任务需要多少时间 |
20 |
15 |
Development |
开发 |
365 |
690 |
· Analysis |
· 需求分析 (包括学习新技术) |
120 |
150 |
· Design Spec |
· 生成设计文档 |
40 |
30 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
15 |
20 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
20 |
10 |
· Design |
· 具体设计 |
30 |
60 |
· Coding |
· 具体编码 |
120 |
350 |
· Code Review |
· 代码复审 |
30 |
30 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
90 |
40 |
Reporting |
报告 |
65 |
90 |
· Test Report |
· 测试报告 |
30 |
20 |
· Size Measurement |
· 计算工作量 |
15 |
10 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
20 |
60 |
三、解题思路:
1)认真阅读武老师给出的任务书,分析基本需求,并将老师文字描述的关键部分复制粘贴到本地文件,根据自己的理解写出需求文档。
2)根据需求分析技术关键点:用户的命令行输入,参数的检测,文件获取和读写,文件内容的扫描,这些功能显然都有相通的关键点:字符串匹配,根据以往的经验,解决此类问题的关键是利用正则表达式。
3)根据老师推荐的java语言,查找java正则表达式的相关函数和用法,参考网址:http://www.runoob.com/java/java-regular-expressions.html;java的字符串分割方法,参考网址:https://www.cnblogs.com/xiaoxiaohui2015/p/5838674.html
4)文件名的确定和读写,参考网址:http://mouselearnjava.iteye.com/blog/1959690;读写文件的方式,参考网址:https://www.cnblogs.com/1175429393wljblog/p/5918150.html
四、程序实现的过程
1)根据java的代码组织,wordcount.java中包括了程序的主逻辑和入口函数,WC.java根据命令行的参数进行相关的操作,readFileByLines.java是对文件进行逐行读写,fileFinder.java是递归查找目录和子目录下所有的指定后缀的文件。
2)类和函数
类名 |
主要函数 |
作用 |
wordcount |
main() |
逻辑判断,传递参数给WC类,写入文件 |
WC |
public WC(String theCommand) |
构造函数,根据命令行参数对相应的属性赋值并调用其他的count函数 |
|
count*(String theFile) |
Count类型的函数,根据正则表达式的结构统计各种数目 |
readFileByLines |
public ArrayList<String> fileString() |
逐行读取指定文件名的文件内容,并保存为字符串数组 |
fileFinder |
findFiles(String filenameSuffix, String currentDirUsed, List<String> currentFilenameList) |
根据文件后缀,文件目录递归寻找符合条件的文件,并保存在第三个参数中(字符串数组) |
五、关键代码说明
1)入口函数:
1 //利用正则表达式检查用户命令行输入参数是否正确 2 String pattern = "wc.exe (-[cwlsa] )+[^(-[cwlsa] )]+(\\.)[^ ]+( -e .+(\\.)txt)?( -o .+(\\.)txt)?"; 3 boolean isMatch = Pattern.matches(pattern, com); 4 //表达式正确则跳出循环,否则继续接受用户输入 5 if(isMatch){ 6 //根据命令行参数,新建WC类,根据WC的flag数组的对应数字是否为1,判断要往文件中逐行写入哪些内容 7 WC wordCount=new WC(com); 8 if(wordCount.flag[0]==1){ 9 System.out.println(wordCount.getFile()+" "+"字符数:"+" "+wordCount.getNumofChar()); 10 try { 11 FileOutputStream out = new FileOutputStream(System.getProperty("user.dir")+"\\"+"result.txt"); 12 OutputStreamWriter outWriter = new OutputStreamWriter(out, "UTF-8"); 13 BufferedWriter bufWrite = new BufferedWriter(outWriter); 14 bufWrite.write(wordCount.getFile()+" "+"字符数:"+" "+wordCount.getNumofChar()); 15 bufWrite.close(); 16 outWriter.close(); 17 out.close(); 18 } 19 catch (Exception e) { 20 // TODO Auto-generated catch block 21 System.out.println("找不到result.txt"); 22 } 23 }
2)WC.java
1 public WC(String theCommand){ 2 //划分-a等参数 3 this.flag=new int[4]; 4 String pattern = "((-[cwlsa] )+)([^(-[cwlsa] )]+(\\.).+)"; 5 // 创建 Pattern 对象 6 Pattern r = Pattern.compile(pattern); 7 // 现在创建 matcher 对象,m.group(1)存储了-a等参数,m.group(3)存储了文件路径以后的内容 8 Matcher m = r.matcher(theCommand); 9 if(m.find()){ 10 // 划分group(3)的字符串,path记录了源文件路径和-e,-o参数 11 String[] path=m.group(3).split(" -e "); 12 //设置统计的源文件 13 this.file=path[0]; 14 if(path.length>1){ 15 //存在-e参数时 16 if(path[1].split(" -o ").length>0){ 17 this.stopList=path[1].split(" -o ")[0]; 18 } 19 //存在-o参数时 20 if(path[1].split(" -o ").length==2){ 21 this.outputFile=path[1].split(" -o ")[1]; 22 } 23 } 24 /* 25 //确定文件路径 26 fileFinder finder = new fileFinder(); 27 List<String> filenameList = new ArrayList<String>(); 28 29 if(file.split("\\*").length>1) 30 //文件中含有* 31 { 32 if(file.split("\\*")[0].length()>0){ 33 //文件中含有绝对路径 34 finder.findFiles(file.split("\\.")[1] ,file.split("\\*")[0] , filenameList);} 35 //文件默认为当前路径 36 else{finder.findFiles(file.split("\\.")[1] ,System.getProperty("user.dir"), filenameList);} 37 } 38 else{ 39 //文件中没有*,即文件名是确定的,默认\为目录的路径 40 if(file.split("\\\\").length==1){ 41 //文件为当前路径 42 43 } 44 else{ 45 //文件为绝对路径 46 47 } 48 49 } 50 */ 51 //没有-s参数,即只用处理该目录下的制定文件 52 if(Pattern.matches("[^(-s )]*(-c )[^(-s )]*",m.group(1))){ 53 countChar(file); 54 flag[0]=1; 55 } 56 if(Pattern.matches("[^(-s )]*(-w )[^(-s )]*",m.group(1))){ 57 countWord(file); 58 flag[1]=1; 59 } 60 if(Pattern.matches("[^(-s )]*(-l )[^(-s )]*",m.group(1))){ 61 countLine(file); 62 flag[2]=1; 63 } 64 if(Pattern.matches("[^(-s )]*(-a )[^(-s )]*",m.group(1))){ 65 countCode(file); 66 flag[3]=1; 67 countEmpty(file); 68 countComment(file); 69 }
3)count类型函数,下面只展示字符的统计,其他均只用更改正则表达式和某些参数
1 //计算每个文件的字符数 2 public void countChar(String theFile){ 3 readFileByLines readFile= new readFileByLines(this.file); 4 //按行读取指定文件 5 ArrayList<String> lineArray = new ArrayList<String> (); 6 lineArray=readFile.fileString(); 7 String pattern="."; 8 Pattern r = Pattern.compile(pattern); 9 int count=0; 10 for(int i =0;i<lineArray.size();i++){ 11 Matcher m = r.matcher(lineArray.get(i)); 12 //System.out.println(lineArray.get(i)+lineArray.size()); 13 while(m.find()){ 14 count++; 15 } 16 } 17 setNumofChar(count+lineArray.size()); 18 19 }
4)readFileByLines.java
1 public ArrayList<String> fileString(){ 2 File file = new File(fileName); 3 ArrayList<String> lineArray = new ArrayList<String> (); 4 BufferedReader reader =null;{ 5 try { 6 reader = new BufferedReader(new FileReader(file)); 7 String tempString = null; 8 // 一次读入一行,直到读入null为文件结束 9 while ((tempString = reader.readLine()) != null) { 10 lineArray.add(tempString); 11 } 12 reader.close(); 13 } catch (IOException e) { 14 //e.printStackTrace(); 15 System.out.println("找不到指定文件,请重新输入"); 16 } finally { 17 if (reader != null) { 18 try { 19 reader.close(); 20 } catch (IOException e1) { 21 } 22 } 23 } 24 } 25 return lineArray; 26 }
5)fileFinder.java
1 public void findFiles(String filenameSuffix, String currentDirUsed, 2 List<String> currentFilenameList) { 3 File dir = new File(currentDirUsed); 4 if (!dir.exists() || !dir.isDirectory()) { 5 System.out.println("不存在该目录"); 6 return; 7 } 8 9 for (File file : dir.listFiles()) { 10 if (file.isDirectory()) { 11 /** 12 * 如果目录则递归继续遍历 13 */ 14 findFiles(filenameSuffix,file.getAbsolutePath(), currentFilenameList); 15 } else { 16 /** 17 * 如果不是目录。 18 * 那么判断文件后缀名是否符合。 19 */ 20 if (file.getAbsolutePath().endsWith(filenameSuffix)) { 21 currentFilenameList.add(file.getAbsolutePath()); 22 } 23 } 24 } 25 } 26
六、测试用例的设计:
1)确定测试方法:根据第二周课程的学习内容,主要应用白盒测试;
2)分析高风险点:命令行输入参数的合法性;命令行参数的划分和获取;文件名的合法性和存在性;程序中正则表达式的正确性;目录的处理;读写文件的编码问题;特殊字符的处理;
3)测试代码的设计思路:根据程序的处理流程,找到各个判断分支,修改相应的参数以达到最大的覆盖,完成程序流程的正确性;在统计文件中输入特殊字符,检测程序正则表达式的健壮性;具体流程可以分为两个方面,一为命令行参数的测试,二为文件内容的测试。
4)具体测试代码详见github项目地址。
七、参考文献:
https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/0013752340242354807e192f02a44359908df8a5643103a000
http://www.runoob.com/java/java-regular-expressions.html
https://www.cnblogs.com/xiaoxiaohui2015/p/5838674.html
https://www.cnblogs.com/franson-2016/p/5728280.html
http://www.sharejs.com/codes/java/5970
http://mouselearnjava.iteye.com/blog/1959690
https://www.cnblogs.com/1175429393wljblog/p/5918150.html