项目地址: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