项目地址:https://github.com/LixinXie/WordCounter
正文
一、WC 项目要求
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 参数。这个参数单独使用。如果命令行有这个参数,则程序会显示图形界面,用户可以通过界面选取单个文件,程序就会显示文件的字符数、行数等全部统计信息。
需求举例:
wc.exe -s -a *.c
返回当前目录及子目录中所有*.c 文件的代码行数、空行数、注释行数。
二、解题思路
使用的编程语言:Java
使用到的工具: JDK 1.8、JUnit 5.4、 Intellij IDEA、Git
需要学习的新知识:文件读取、图形化界面、字符串的相关操作、正则表达式等知识
在开始拿到题目时我是很懵的,以前没有接触过这样的作业,也不知道如何下手。通过阅读资料,查看作业详情,和同学交流,逐渐的了解这个作业改怎么做,项目该怎么构建。一开始对Java的知识并不是很熟悉,通过做这个个人作业,我坚持着做中学,边做边学,一步一步的完成。
三、设计实现过程
在设计初期,我估计基本功能需要三个类来实现,一个主类,一个GUI类,一个测试类;
主类实现基本功能和扩展功能中的-a功能,主类中有基本功能的三个方法(-c、-w、-l)和特殊行(-a)的方法,这几个方法都是以有返回值的形式存在的,而不是直接在该方法中输出。后来为了实现递归遍历文件/文件夹,又在主类中加入了能返回文件绝对路径List<String>对象的方法,在这个方法里使用递归思想来获取文件名(绝对路径);GUI图形用户界面选择文件的类可以直接运行(需要调用主类里的方法)这里只实现了选择文件然后输出该文件的信息(这个信息通过弹窗对话来提示);测试类用来测试主类里面的各个方法(这里使用了JUnit的@Test注解让测试类中的每个方法能独立运行)。
后来在构建过程中,发现特殊行的统计时三个值返回的问题,因此增加了一个类来封装特殊行的三个值,这样在统计特殊行和获取特殊行的数值时就能返回三个值了。
四、关键代码and设计说明
1.实现统计字符数(包括中文字符,不含换行符)
注意:在UTF-8编码下和GBK编码下,同一个源文件的字符数统计可能不一样
说明:在统计字符数时,使用了输入流InputStreamReader来读取每一行,BufferedReader缓存读到的每一行;再将缓存的每一行转化为字符串,统计各个字符串的长度之和就是所有字符的总数,但这里不包括每一行末尾的换行符。
1 public static int charcount(String filename){
2 //统计字符数,不包含换行
3 int characters=0;
4 String str = null;
5 BufferedReader br = null;
6 InputStreamReader reader = null;
7 File file = new File(filename);
8 try {
9 reader = new InputStreamReader(new FileInputStream(file));
10 br = new BufferedReader(reader);
11 while((str = br.readLine())!=null){
12 characters+=str.length();
13 }
14
15 } catch (Exception e) {
16 e.printStackTrace();
17 }finally{
18 if(reader!=null){
19 try {
20 reader.close();
21 } catch (IOException e) {
22 e.printStackTrace();
23 }
24 }
25 if(br!=null){
26 try {
27 br.close();
28 } catch (IOException e) {
29 e.printStackTrace();
30 }
31 }
32 }
33 return characters;
34 }
2.实现统计单词总数
说明:因为单词可以有任意长度,并且单词可在注释中存在,故任何连续的字母串都将被认为是单词
1 public static int wordcount(String filename){
2 //统计单词数
3 int words=0;
4 String str = null;
5 BufferedReader br = null;
6 try {
7 br = new BufferedReader(new FileReader(filename));
8 Pattern pattern = Pattern.compile("[a-zA-Z]+");
9 while((str=br.readLine())!=null){
10 Matcher matcher = pattern.matcher(str);
11 while(matcher.find()){
12 words++;
13 }
14 }
15
16 } catch (Exception e) {
17 e.printStackTrace();
18 }finally {
19 if(br!=null){
20 try {
21 br.close();
22 } catch (IOException e) {
23 e.printStackTrace();
24 }
25 }
26 }
27 return words;
28 }
3.实现统计总行数
说明:统计读取到的行数
1 public static int linecount(String filename){
2 //统计总行数
3 int lines=0;
4 File file = new File(filename);
5 BufferedReader br = null;
6 try {
7 br = new BufferedReader(new FileReader(file));
8 while(br.readLine() != null) {
9 lines++;
10 }
11
12 } catch (Exception e) {
13 e.printStackTrace();
14 }finally{
15 if(br!=null){
16 try {
17 br.close();
18 } catch (IOException e) {
19 e.printStackTrace();
20 }
21 }
22 }
23 return lines;
24 }
4.为特殊行封装一个类
说明:这样可以在统计特殊行时可以返回多个值
1 public class Special { //为特殊行封装一个类,以便于返回特殊行时能返回多个值
2 private int nulllines=0;
3 private int codelines=0;
4 private int commentlines=0;
5
6 public int getNulllines() {
7 return nulllines;
8 }
9
10 public int getCodelines() {
11 return codelines;
12 }
13
14 public int getCommentlines() {
15 return commentlines;
16 }
17
18 public void setNulllines(int nulllines) {
19 this.nulllines = nulllines;
20 }
21
22 public void setCodelines(int codelines) {
23 this.codelines = codelines;
24 }
25
26 public void setCommentlines(int commentlines) {
27 this.commentlines = commentlines;
28 }
29 }
5.实现统计特殊行(空行、代码行、注释行)
说明:
空行----该行全部是空格或格式控制字符,如果包括代码,则只有不超过一个可显示的字符,例如“{”和“}”
代码行----本行包括多于一个字符的代码
注释行----本行不是代码行,并且本行包括注释,或者是} //注释等情况
1 public static Special speciallinecount (String filename){
2 int nulllines=0;
3 int codelines=0;
4 int commentlines=0;
5 Special sp = new Special();
6 String str = null;
7 File file = new File(filename);
8 BufferedReader br = null;
9 try {
10 br = new BufferedReader(new FileReader(file));
11 while((str=br.readLine())!= null) {
12 if(str.length()==0){
13 nulllines++;
14 }else{
15 String substr = str.trim(); //去除读到的每行字符串的首尾空格
16 if(substr.equals("{") || substr.equals("}")){
17 nulllines++;
18 } else if(substr.indexOf("//")==0 || substr.indexOf("}//")==0 || substr.indexOf("{//")==0){
19 commentlines++;
20 } else if(substr.indexOf("/*")==0 || substr.indexOf("}/*")==0 || substr.indexOf("{/*")==0){
21 commentlines++;
22 while((str=br.readLine())!= null){
23 commentlines++;
24 if(str.trim().contains("*/"))
25 break;
26 }
27 } else{
28 codelines++;
29 }
30 }
31 }
32 sp.setNulllines(nulllines);
33 sp.setCodelines(codelines);
34 sp.setCommentlines(commentlines);
35 } catch (Exception e) {
36 e.printStackTrace();
37 }finally{
38 if(br!=null){
39 try {
40 br.close();
41 } catch (IOException e) {
42 e.printStackTrace();
43 }
44 }
45 }
46 return sp;
47 }
6.递归遍历文件夹/文件,输出文件的信息
说明:getFileList方法实现了递归将文件夹中的文件存放在List<String>中,traverse方法实现了将获取到的文件名List逐个进行操作并输出对应的文件信息
1 public static void traverse(String filename) {
2 List<String> fileList = new ArrayList<>();
3 fileList = getFileList(filename);
4 for (String file : fileList) {
5 int characters = 0;
6 int words = 0;
7 int lines = 0;
8 int nulllines = 0;
9 int codelines = 0;
10 int commentlines = 0;
11 Special sp = new Special();
12 characters = wordcounter.charcount(file);
13 words = wordcounter.wordcount(file);
14 lines = wordcounter.linecount(file);
15 sp = wordcounter.speciallinecount(file);
16 nulllines = sp.getNulllines();
17 codelines = sp.getCodelines();
18 commentlines = sp.getCommentlines();
19
20 System.out.println("文件" + file + "的信息如下:");
21 System.out.println("字符总数:" + characters);
22 System.out.println("单词总数:" + words);
23 System.out.println("总行数:" + lines);
24 System.out.println("空行数:" + nulllines);
25 System.out.println("代码总行数:" + codelines);
26 System.out.println("注释总行数:" + commentlines);
27 System.out.println();
28 }
29 }
30 public static List<String> fileList = new ArrayList<>(); //构建一个全局变量的list来装递归遍历的文件/文件夹
31
32 public static List<String> getFileList(String path){ //递归获取文件路径并装入List
33 File filepath = new File(path);
34 File [] files = filepath.listFiles();
35 if(files!=null){
36 for(int i=0;i<files.length;i++){
37 if(files[i].isDirectory()){
38 getFileList(files[i].getAbsolutePath());
39 }else if(files[i].isFile() && files[i].canRead()){
40 String strfilename = files[i].getAbsolutePath();
41 fileList.add(strfilename);
42 }else{
43 continue;
44 }
45 }
46 }
47 return fileList;
48 }
五、测试运行截图
1.对单个文件进行基本功能测试



2.进行图形界面选取文件,并显示文件的信息



3.递归遍历F盘下的123文件夹







六、遇到的困难及解决方法
- 遇到的问题:
1.使用java设计程序,对java的基础知识不太熟悉
解决办法:边做边查边学。
收获:了解部分Java知识并加以应用。
2.遇到GBK编码的源文件统计字符时字符个数不正确。
解决办法:将源文件的编码格式改成UTF-8就能正确统计字符数了。
收获:了解到UTF-8格式和GBK格式的编码字符数不是一样的。
3.遇到递归遍历文件夹时只能读到第一层文件
解决办法:经过查资料和调试,创建一个全局的list来装递归遍历的文件/文件夹就解决了。
七、PSP
| PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | ||
| · Estimate | · 估计这个任务需要多少时间 | 900 | 1083 |
| Development | 开发 | ||
| · Analysis | · 需求分析 (包括学习新技术) | 60 | 100 |
| · Design Spec | · 生成设计文档 | 10 | 8 |
| · Design Review | · 设计复审 (和同事审核设计文档) | 10 | 2 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 30 |
| · Design | · 具体设计 | 100 | 90 |
| · Coding | · 具体编码 | 500 | 570 |
| · Code Review | · 代码复审 | 60 | 60 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 120 | 130 |
| Reporting | 报告 | ||
| · Test Report | · 测试报告 | 60 | 60 |
| · Size Measurement | · 计算工作量 | 10 | 8 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 25 |
| 合计 | 1880 | 2166 |
八:小结
在实现这个项目过程中,通过不断摸索,查资料,逐步了解了一点Java知识;设计前的预估与设计时的实际情况还是有差别的,时间总是要预留多一点;在设计构建过程中,认识到构建一个项目,做好计划是项目的第一步,没有计划的项目是不知道从哪里开始的。
来源:https://www.cnblogs.com/lixin-xie/p/12457558.html