Github项目地址:https://github.com/Aliiiiiiiiiiiiiiiii/Handsome-boy
一.项目要求
wc.exe 是一个常见的工具,它能统计文本文件的字符数、单词数和行数。这个项目要求写一个命令行程序,模仿已有wc.exe 的功能,并加以扩充,给出某程序设计语言源文件的字符数、单词数和行数。
实现一个统计程序,它能正确统计程序文件中的字符数、单词数、行数,以及还具备其他扩展功能,并能够快速地处理多个文件。
具体功能要求:
程序处理用户需求的模式为:wc.exe [parameter] [file_name]
基本功能列表:
wc.exe -c file.c //返回文件 file.c 的字符数
wc.exe -w file.c //返回文件 file.c 的词的数目
wc.exe -l file.c //返回文件 file.c 的行数
扩展功能:
-s 递归处理目录下符合条件的文件。
-a 返回更复杂的数据(代码行 / 空行 / 注释行)。
空行:本行全部是空格或格式控制字符,如果包括代码,则只有不超过一个可显示的字符,例如“{”。
代码行:本行包括多于一个字符的代码。
注释行:本行不是代码行,并且本行包括注释。一个有趣的例子是有些程序员会在单字符后面加注释:
} //注释
在这种情况下,这一行属于注释行。
[file_name]: 文件或目录名,可以处理一般通配符。
高级功能:
-x 参数。这个参数单独使用。如果命令行有这个参数,则程序会显示图形界面,用户可以通过界面选取单个文件,程序就会显示文件的字符数、行数等全部统计信息。
二.PSP表格
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
120 | 90 |
· Estimate |
· 估计这个任务需要多少时间 |
30 | 60 |
Development |
开发 |
1440 | 1440 |
· Analysis |
· 需求分析 (包括学习新技术) |
120 | 120 |
· Design Spec |
· 生成设计文档 |
30 | 50 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
20 | 30 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
10 | 20 |
· Design |
· 具体设计 |
60 | 90 |
· Coding |
· 具体编码 |
50 | 100 |
· Code Review |
· 代码复审 |
600 | 780 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
300 | 250 |
Reporting |
报告 |
150 | 150 |
· Test Report |
· 测试报告 |
60 | 90 |
· Size Measurement |
· 计算工作量 |
5 | 10 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
20 | 20 |
合计 |
1305 | 1620 |
三.解题思路
刚拿到题目的时候我并没有想到使用正则表达式去做匹配功能,后来跟同学讨论才知道的,看到对文件的操作就自然想到了学过的Java的输入输出流,对文件的操作也是比较了解的,所以选择了使用java去实现wc功能,至于这么多功能,我选择分模块去写代码,一个类实现一个功能,然后写一个main函数利用switch对用户输入的操作指令进行各项功能的实现,或许会有一些代码重复,但是思路直接,实现起来思路会清晰。统计字符使用正则表达式很容易就实现了,统计单词利用Pattern的split()方法把[^a-zA-Z]的分隔开,利用String[]存储再统计,行的统计就直接读一行就LineCount++。至于图形化界面,之前有学过相关的知识,所以是实现起来比较快,主要是输出文件的内容以及-c,-w,-l的实现。
四.设计实现过程
通过用户的输入而进入不同的功能模块,每个功能为一个类,通过while循环包含着switch语句进行可重复的操作
五.代码说明
主函数代码
package wc; import java.io.IOException; import java.util.Scanner; public class Start { public static void main(String[] args) throws IOException { // TODO 自动生成的方法存根 System.out.println("Welcome to wc.exe!\n"); System.out.println("----------指令参考-----------"); System.out.println("wc.exe -c 返回文件的字符数"); System.out.println("wc.exe -w 返回文件的单词数"); System.out.println("wc.exe -l 返回文件的行数"); System.out.println("wc.exe -s 递归处理目录下符合条件的文件"); System.out.println("wc.exe -a 返回代码行 / 空行 / 注释行"); System.out.println("wc.exe -x 显示图形界面"); System.out.println("wc.exe -exit 退出"); System.out.println("-------------------------------------\n"); while(true) { System.out.print("请输入指令: wc.exe -"); Scanner s=new Scanner(System.in); String in= s.nextLine(); switch(in) { //输入不同的指令打开不同的类,实现不同的功能 case "c": CharCount cc=new CharCount(); break; case "w": WordCount wc=new WordCount(); break; case "l": LineCount lc=new LineCount(); break; case "s": SCount sc=new SCount(); break; case "a": ACount ac=new ACount(); break; case "x": HighLevel hl=new HighLevel(); break; case "exit": System.out.println("Good Bye!"); System.exit(0); default: System.out.println("请输入正确的命令"); } } } }
CharChout 字符统计
package wc; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; public class CharCount { String REGEX="\\S"; //非空白字符 CharCount() throws IOException{ Pattern p=Pattern.compile(REGEX); //利用正则表达式Pattern类以及Matcher类 System.out.println("请输入文件的绝对路径:"); System.out.print("wc.exe -c "); Scanner inc=new Scanner(System.in); String path=inc.nextLine(); BufferedReader fis=new BufferedReader(new FileReader(path)); String s; int charcount=0; while((s=fis.readLine())!=null) { Matcher m=p.matcher(s); //对正则表达式进行模式匹配 while(m.find()) charcount++; } System.out.println("该文件有"+charcount+"个字符\n\n"); fis.close(); } }
WordCount 单词统计
public class WordCount { String REGEX="[^a-zA-Z]"; WordCount() throws IOException{ Pattern p=Pattern.compile(REGEX); System.out.println("请输入文件的绝对路径:"); System.out.print("wc.exe -w "); Scanner inw=new Scanner(System.in); String path=inw.nextLine(); BufferedReader fis=new BufferedReader(new FileReader(path)); String s; int wordcount=0; while((s=fis.readLine())!=null) { if(s.isEmpty()==false) { //要不是一行空行才进行下面的操作,不然要是有空行的话结果不对 String[] letter=p.split(s); for(String word : letter) wordcount++; } } System.out.println("该文件有"+wordcount+"个单词\n\n"); fis.close(); } }
LineCount 行数统计
public class LineCount { LineCount() throws IOException{ System.out.println("请输入文件的绝对路径"); System.out.print("wc.exe -l "); Scanner inl=new Scanner(System.in); String path=inl.nextLine(); BufferedReader fis=new BufferedReader(new FileReader(path)); String s; int linecount=0; while((s=fis.readLine())!=null) { //直接读到一行就+1 linecount++; } System.out.println("该文件有"+linecount+"行\n\n"); fis.close(); } }
Scount 递归处理目录下符合条件的文件
public class SCount { SCount() throws IOException{ System.out.print("请输入要执行的操作:wc.exe -s -"); Scanner control=new Scanner(System.in); String ct= control.nextLine(); System.out.print("请输入要递归处理的目录路径:"); Scanner in=new Scanner(System.in); String path= in.nextLine(); System.out.print("请输入要递归处理的文件类型:wc.exe -s -"+ct+" *."); Scanner style=new Scanner(System.in); String st= style.nextLine(); File file=new File(path); String[] name=file.list(); String s; int count=0; switch(ct) { case "c": Pattern cp=Pattern.compile("\\S"); for(String n:name) { if(n.matches("\\S+."+st)) { //找出目录下符合文件后缀的文件 BufferedReader fis=new BufferedReader(new FileReader(path+"\\"+n)); while((s=fis.readLine())!=null) { //这里的操作跟前面的模块一样 Matcher m=cp.matcher(s); while(m.find()) count++; } System.out.println(n+" 中有"+count+"个字符"); count=0; fis.close(); } } break; case "w": Pattern wp=Pattern.compile("[^a-zA-Z]"); for(String n:name) { if(n.matches("\\S+."+st)) { BufferedReader fis=new BufferedReader(new FileReader(path+"\\"+n)); while((s=fis.readLine())!=null) { if(s.isEmpty()==false) { String[] letter=wp.split(s); for(String word : letter) count++; } } System.out.println(n+" 中有"+count+"个单词"); count=0; fis.close(); } } break; case "l": for(String n:name) { if(n.matches("\\S+."+st)) { BufferedReader fis=new BufferedReader(new FileReader(path+"\\"+n)); while((s=fis.readLine())!=null) { count++; } System.out.println(n+" 中有"+count+"行"); count=0; fis.close(); } } break; case "a": } } }
ACount 返回更复杂的数据(代码行 / 空行 / 注释行)
public class ACount { int total=0; int EmptyLine=0; int DescribeLine=0; int CodeLine=0; int tag=0; //设置一个标志,要tag=0,证明这一行还没扫描到“/*”,tag=1说明已经扫到了 ACount() throws IOException{ Pattern p1=Pattern.compile("//"); Pattern p2=Pattern.compile("//*"); Pattern p3=Pattern.compile("/*/"); System.out.println("请输入文件的绝对路径:"); System.out.print("wc.exe -a "); Scanner ina=new Scanner(System.in); String path=ina.nextLine(); BufferedReader fis=new BufferedReader(new FileReader(path)); String s; while((s=fis.readLine())!=null) { total++; if(tag==0) { //根据tag的值进行不同的操作 Matcher m1=p1.matcher(s); Matcher m2=p2.matcher(s); if(s.isEmpty()||(s.startsWith("{")&&s.endsWith("{"))) //只有tag=0时才计算空行,要是在多行注释里面出现的空行算入注释行而不算空行 EmptyLine++; else if(m1.find()) DescribeLine++; else if(m2.find()) { DescribeLine++; tag=1; } } else { DescribeLine++; Matcher m3=p3.matcher(s); if(m3.find()) //当匹配到“*/”的时候,说明多行注释结束,把tag置为0 tag=0; } } CodeLine=total-DescribeLine-EmptyLine; System.out.println("该文件有"+EmptyLine+"行空行"); System.out.println("该文件有"+DescribeLine+"行注释行"); System.out.println("该文件有"+CodeLine+"行代码行\n\n"); fis.close(); } }
- x 显示图形界面的代码不在这里分析了,详细见Github源码
六.测试运行
七.项目小结
对于SCount以及ACount这两个类的语言逻辑,我出bug很多次了,总会存在一些小问题,后来发现自己的逻辑不够严谨,导致了运行结果的错误,而且整个程序还存在着或多或少的问题,有一些解决方法是我在上下学路上时候想到的,或许不是什么好的方法,但是这是我自己思考得出的方法,觉得通过这个项目的编程,我收获了不少,尽管最后做出来的程序有不足,但是通过自己思考和努力,锻炼了自己的编程能力,受益匪浅。