Github项目地址:https://cannotfindtheid.github.io/WordCount/
PSP表格
PSP2.1 |
PSP阶段 |
预估耗时 (小时) |
实际耗时 (小时) |
Planning |
计划 |
1 | 1 |
· Estimate |
· 估计这个任务需要多少时间 |
1 | 1 |
Development |
开发 |
23 | 29 |
· Analysis |
· 需求分析 (包括学习新技术) |
2 | 2 |
· Design Spec |
· 生成设计文档 |
2 | |
· Design Review |
· 设计复审 (和同事审核设计文档) |
1 | |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
1 | 1 |
· Design |
· 具体设计 |
3 | 3 |
· Coding |
· 具体编码 |
10 | 15 |
· Code Review |
· 代码复审 |
1 | 3 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
3 | 5 |
Reporting |
报告 |
4 | 2 |
· Test Report |
· 测试报告 |
2 | 2 |
· Size Measurement |
· 计算工作量 |
1 | |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
1 | |
合计 |
28 | 32 |
解题思路:
刚开始看到需求时,我的第一个疑问就是:以前我们接触到的都是直接在程序中得到参数,或者通过键盘输入得到参数,而这次却是通过命令行传参数。那么,获得参数之后要怎么进行解析、调用对应函数、向各个函数中传参呢?
于是我开始在网上查找命令行参数解析的方法,并在博客[1]中了解到了一些命令行选项解析工具,并决定使用Commons-cli。
大致了解后,我开始思考如何实现各个功能。这些都将在程序设计和代码实现中详细说明,这里略过。
然后我了解了一下jar包的生成和exe文件的生成过程,包括jre环境的设置,这里参考了博客[2]。
程序设计实现过程:
程序共包含三个类,在同一个Main包下:
Count类:
私有成员属性:字符数、单词数、行数、代码行数、空行数、注释行数,都有其get方法;
parse方法:解析给定文件,并给各成员属性赋值
(如果指定了停用词表,调用stop方法,得到所有停用词,在计算单词数时不参与)
stop方法:由给出的停用词表名,得到所有的停用词并封装到一个ArrayList中
FileChoose类:
chooseFile方法:显示文件选择图形界面,获取所选的文件的绝对路径
outputToFile方法:将一个ArrayList<String>输出到指定文件
findFile方法:找到指定文件夹下所有符合条件的文件的绝对路径
showResult方法:用图形界面显示结果
Main类:
成员变量:parseFilename,要进行解析的文件名
main函数:进行命令行解析并调用响应函数。先解析出各option对应的参数,由于-c、-w、-l、-a、-s共用一个参数,要先得到optionValue值不为空的值,并赋给parseFilename;当parseFilename不为空时,对此文件进行解析,并调用Count类中相应属性的get方法,得到其值,并加入一个ArrayList中。对-o、-x、-s,也是调用相应方法。
代码说明:
(1)Count.parse函数:用BufferedReader.readLine()按行读取并解析文件,得到字符数、单词数、行数、详细行数
1 public void parse(String name,ArrayList<String> stopList){ 2 String s; 3 String wordSplit1[]=null; 4 String wordSplit2[]=null; 5 char c1; 6 char c2 = 0; 7 FileReader fr=null; 8 BufferedReader br=null; 9 try { 10 fr=new FileReader(new File(name)); 11 br=new BufferedReader(fr); 12 int flag3=0;//多行注释行 13 14 //计算单词数 15 while((s=br.readLine())!=null){ 16 if(flag3==2)flag3=0; 17 18 //空格分割的单词组 19 wordSplit1=s.split(" "); 20 for(int i=0;i<wordSplit1.length;i++){ 21 //得到多个单词 22 wordSplit2=wordSplit1[i].split(","); 23 wordNum+=wordSplit2.length; 24 25 for(int j=0;j<wordSplit2.length;j++){ 26 if(wordSplit2[j].equals(""))wordNum--; 27 28 if(stopList!=null) 29 for(int k=0;k<stopList.size();k++) 30 //如果有禁用列表中的或空字符串,不算单词数 31 if(!stopList.get(k).equals("")&&wordSplit2[j].equals(stopList.get(k)))wordNum--; 32 33 } 34 } 35 36 //计算各行数 37 lineNum++; 38 charNum+=s.length(); 39 int flag1=0;//此行可看做代码的字符数 40 int flag2=0;//判断是否有单行注释 41 42 43 //按行解析 44 for(int i=0;i<s.length();i++){ 45 c1=s.charAt(i); 46 47 if(i<=s.length()-2&&c1!=44){ 48 c2=s.charAt(i+1); 49 //此字符和后一个字符为“//”,为单行注释 50 if(c1==47&&c2==47)flag2=1; 51 //多行注释开始 52 if(c1==47&&c2==42)flag3=1; 53 //多行注释结束 54 if(c1==42&&c2==47)flag3=2; 55 } 56 if(flag2==0)flag1++; 57 } 58 59 //得到此行类型 60 if(flag3!=0)noteLine++; 61 else if(flag1==0||flag1==1){ 62 if(flag2==0)blankLine++; 63 if(flag2==1)noteLine++; 64 } 65 else codeLine++; 66 } 67 68 69 } catch (Exception e) { 70 // TODO Auto-generated catch block 71 e.printStackTrace(); 72 }finally{ 73 try { 74 br.close(); 75 fr.close(); 76 } catch (IOException e) { 77 // TODO Auto-generated catch block 78 e.printStackTrace(); 79 } 80 81 } 82 }
(2)选择文件的图形界面和显示结果的图形界面
1 public class FileChoose { 2 3 //图形界面选择文件 4 public String chooseFile(){ 5 JFileChooser jfc=new JFileChooser(); 6 jfc.setDialogTitle("请选择一个文件"); 7 jfc.showOpenDialog(null); 8 jfc.setVisible(true); 9 10 //得到用户选择的文件路径 11 String filename=jfc.getSelectedFile().getAbsolutePath(); 12 return filename; 13 }
1 public void showResult(ArrayList<String> content){ 2 JFrame jf=new JFrame("统计结果"); 3 jf.setSize(500, 300); 4 jf.setLocation(500, 400); 5 jf.setVisible(true); 6 //关闭时释放内存 7 jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 8 //文本域 9 JTextArea jta=new JTextArea(); 10 jf.add(jta); 11 12 for(int i=0;i<content.size();i++){ 13 jta.append(content.get(i)+"\n"); 14 }
(3)将字符串列表输出到指定文件:
1 public void outputToFile(String filename,ArrayList<String> content){ 2 FileWriter fw = null; 3 4 try { 5 fw=new FileWriter(filename); 6 for(int i=0;i<content.size();i++){ 7 fw.write(content.get(i)); 8 } 9 10 } catch (IOException e) { 11 // TODO Auto-generated catch block 12 e.printStackTrace(); 13 }finally{ 14 try { 15 fw.close(); 16 } catch (IOException e) { 17 // TODO Auto-generated catch block 18 e.printStackTrace(); 19 } 20 } 21 }
(4)找到文件夹下所有符合的文件(文件名用绝对路径):
1 public void findFiles(String upperFile,ArrayList<String> fileNames,String matchName){ 2 File f=new File(upperFile); 3 File[] files; 4 //如果是文件夹 5 if(f.isDirectory()){ 6 //获取所有子文件 7 files=f.listFiles(); 8 String absolutePath; 9 //判断每个子文件是文件还是文件夹 10 for(int i=0;i<files.length;i++){ 11 absolutePath=files[i].getAbsolutePath(); 12 13 //如果是文件且后缀匹配 14 if(files[i].isFile()&&absolutePath.endsWith(matchName.substring(1))){ 15 fileNames.add(absolutePath); 16 } 17 //如果是文件夹,则继续查找 18 else if(files[i].isDirectory()) 19 findFiles(absolutePath,fileNames,matchName); 20 } 21 } 22 }
(5)main函数,解析命令行并调用各函数:
1 public static void main(String[] args) { 2 DefaultParser parser=new DefaultParser(); 3 CommandLine cl=null; 4 5 //定义选项 6 Options options=new Options(); 7 8 options.addOption(Option.builder("c").optionalArg(true).numberOfArgs(1).build());//统计字符数 9 options.addOption(Option.builder("w").optionalArg(true).numberOfArgs(1).build());//统计单词数 10 options.addOption(Option.builder("l").optionalArg(true).numberOfArgs(1).build());//统计行数 11 options.addOption(new Option("o",true,"OUTPUTFILE"));//输出到指定文件 12 options.addOption(Option.builder("s").optionalArg(true).numberOfArgs(1).build());//递归处理目录下所有文件 13 options.addOption(Option.builder("a").optionalArg(true).numberOfArgs(1).build());//更复杂的数据(代码行、空行、注释行) 14 options.addOption(new Option("e",true,"STOPLIST"));//停用词表 15 options.addOption(new Option("x","CHOOSEFILE"));//图形界面选取文件 16 17 try { 18 //解析命令行 19 cl=parser.parse(options, args); 20 ArrayList<String> content = new ArrayList<String>(); 21 FileChoose fileChoose=new FileChoose(); 22 String str; 23 Count count=new Count(); 24 25 //有-x 26 if(cl.hasOption("x")){ 27 parseFilename=fileChoose.chooseFile(); 28 count.parse(parseFilename, null); 29 30 content.add(parseFilename+",字符数:"+count.getCharNum()+"\r\n"); 31 content.add(parseFilename+",单词数:"+count.getWordNum()+"\r\n"); 32 content.add(parseFilename+",行数:"+count.getLineNum()+"\r\n"); 33 content.add(parseFilename+",代码行/空行/注释行:"+count.getCodeLine()+"/"+count.getBlankLine()+"/"+count.getNoteLine()+"\r\n"); 34 fileChoose.showResult(content); 35 } 36 37 else if(!cl.hasOption("x")){ 38 //无-x时 39 int c=0,w=0,l=0,a=0,s=0,e=0; 40 ArrayList<String> files; 41 ArrayList<String> stopList = new ArrayList<String>(); 42 43 //检查各option,得到参数(解析文件名) 44 if(cl.hasOption("c")){ 45 c=1; 46 if((str=cl.getOptionValue("c"))!=null)parseFilename=str; 47 } 48 49 50 if(cl.hasOption("w")){ 51 w=1; 52 if((str=cl.getOptionValue("w"))!=null)parseFilename=str; 53 } 54 55 if(cl.hasOption("l")){ 56 l=1; 57 if((str=cl.getOptionValue("l"))!=null)parseFilename=str; 58 } 59 60 if(cl.hasOption("a")){ 61 a=1; 62 if((str=cl.getOptionValue("a"))!=null)parseFilename=str; 63 } 64 65 if(cl.hasOption("s")){ 66 s=1; 67 if((str=cl.getOptionValue("s"))!=null)parseFilename=str; 68 } 69 70 if(cl.hasOption("e")){ 71 e=1; 72 String stopListFile=cl.getOptionValue("e"); 73 stopList=count.stop(stopListFile); 74 } 75 76 if(parseFilename!=null){ 77 files=new ArrayList<String>(); 78 79 //如果有-s,files为所有文件,如果无-s,files只有parseFilename一个文件 80 if(s==1){ 81 //file表示根目录 82 File file=new File(""); 83 fileChoose.findFiles(file.getAbsolutePath(), files, parseFilename); 84 } 85 else{ 86 files.add(parseFilename); 87 } 88 89 90 for(int i=0;i<files.size();i++){ 91 92 //根据是否有停用词表,进行解析 93 if(e==1){ 94 //有停用词表 95 count.parse(files.get(i), stopList); 96 } 97 else{ 98 //无停用词表 99 count.parse(files.get(i), null); 100 } 101 102 //有-c 103 if(c==1){ 104 int charCount=count.getCharNum(); 105 content.add(files.get(i)+",字符数:"+charCount+"\r\n"); 106 } 107 108 //有-w 109 if(w==1){ 110 int wordCount=count.getWordNum(); 111 content.add(files.get(i)+",单词数:"+wordCount+"\r\n"); 112 } 113 114 //有-l 115 if(l==1){ 116 int lineCount=count.getLineNum(); 117 content.add(files.get(i)+",行数:"+lineCount+"\r\n"); 118 } 119 120 //有-a 121 if(a==1){ 122 content.add(files.get(i)+",代码行/空行/注释行:"+count.getCodeLine()+"/"+count.getBlankLine()+"/"+count.getNoteLine()+"\r\n"); 123 } 124 } 125 126 127 //有-o 128 if(cl.hasOption("o")){ 129 if((str=cl.getOptionValue("o"))!=null) 130 fileChoose.outputToFile(str, content); 131 } 132 else fileChoose.outputToFile("result.txt", content); 133 } 134 } 135 136 137 138 139 140 141 } catch (ParseException e) { 142 // TODO Auto-generated catch block 143 e.printStackTrace(); 144 } 145 146 147 }
测试过程设计:
测试用例:
(1)需要解析的文件
1 #include<stdio.h> 2 void transfer(int n) 3 { 4 if (n > 10)transfer(n / 10); 5 printf("%c", ‘0‘ + n % 10); 6 } 7 void main() 8 { 9 int n; 10 printf("Please input a number:\n"); 11 scanf("%d", &n); 12 transfer(n); 13 }//this is a noteLine 14 15 void show(){ 16 17 ; 18 ;/*noteLine 19 20 hello,this is noteLine 21 right? 22 */ 23 ;//me too 24 }
可以看出,此文件中有空白行、前面单字符的注释行、仅有一个可见字符的行、多行注释行、代码行、多行注释中的空行等可能导致高风险的情况。
(2)wc.exe -c -w -l -a filename
测试-c、-w、-l、-a功能是否正常,即能否输出正确数据到默认“result.txt”文件
(3)wc.exe -a -l -c -w filename
打乱顺序,看输出到文件中的顺序是否依然不变
(4)wc.exe -c filename -o outputname
是否能输出到指定文件
(5)wc.exe -o outputname
未指定解析的文件时,无法输出
(6)wc.exe -c filename -o
未指定要输出到的文件名,无法输出
(7)wc.exe -a -c -s *.txt
能否得到根目录及其子目录下所有符合“*.txt”的文件并输出正确结果
(8)wc.exe -x
单独使用-x
(9)wc.exe -x -c filename
使用-x后又使用-c,也不会将结果输出到文件
(10)wc.exe -w filename -e stoplistfile
停用词列表是否对生效
(11)wc.exe -c -a -l filename -e stoplistfile
停用词列表是否会对其他数据有影响
参考文献链接:
[1]http://rensanning.iteye.com/blog/2161201
[2]http://blog.csdn.net/sunkun2013/article/details/13167099