文本统计工具(Java)
WordCount
GitHub地址:https://github.com/soperfect/SE_work2_TextCounting
一、项目相关要求
-
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 返回更复杂的数据(代码行 / 空行 / 注释行)。 空行:本行全部是空格或格式控制字符,如果包括代码,则只有不超过一个可显示的字符,例如“{”。 代码行:本行包括多于一个字符的代码。 注释行:本行不是代码行,并且本行包括注释。一个有趣的例子是有些程序员会在单字符后面加注释: } //注释 在这种情况下,这一行属于注释行。 特殊情况,如 return; } //注释,既算是代码行也算是注释行。 [file_name]: 文件或目录名,可以处理一般通配符。
-
高级功能
-x 参数。这个参数单独使用。如果命令行有这个参数,则程序会显示图形界面。
用户可以通过界面选取单个文件,程序就会显示文件的字符数、行数等全部统计信息。
二、解题思路
-
算上扩展功能和高级功能,需要实现的功能如下
-
统计字符数:每次读取文本的一行,统计这一行的字符数,然后累加
-
统计单词数:将文中的中文以及其他符号用
" "取代,然后用空格分割取代后的字符串,然后分割后得到的数组转换成集合,使用集合的remove的功能去除集合中的" ",去除后集合的长度就是词的数目。其中使用的guava的Multiset。 -
统计行数:一次读取一行,累加行数
-
递归处理:判断路径是不是目录,不是则报错,是的话把目录下的文件添加到集合里,目录下的子目录再递归处理,最后返回文件路径的集合。
-
返回复杂的数据:按所要求的进行判断累加。
-
通配符匹配:只能匹配
*和?,如果只有单独一个?则会报错,匹配后把符合条件的文件名添加到集合中,通配符中含有其他的符号或者没有这两个字符,最后返回空集合。 - 图形界面:用Java提供的Swing组件进行绘图
-
-
拿到题目后查找了I/O流的操作方法,以及正则表达式的书写,还有字符数的定义。
三、设计实现过程
-
整个项目有三个类,一个存放主函数的类
McMain,一个存放功能函数的类McUtils,一个是图形界面的类McView。 -
主函数的逻辑图如下,在接收命令行传入的字符串之后,
-x参数具有最高优先级,-s具有第二优先级,优先判断是否含有这两个命令。![]()
四、代码说明
这里主要展示用于统计的功能函数的代码
-
返回文件字符数
public static int characterCounting(BufferedReader br) throws IOException { int countChar = 0; //用于接收每一行的内容 String line = null; while ((line = br.readLine()) != null) { countChar += line.length(); } return countChar; }
-
返回文件单词数
public static int wordCounting(BufferedReader br) throws IOException { //用于接收每一行的内容 String line = null; StringBuffer stringBuffer=new StringBuffer(); while ((line = br.readLine()) != null) { stringBuffer.append(line); } line = stringBuffer.toString(); line = line.replaceAll("[^a-zA-Z\\s+]", " "); String[] strings=line.split("[\\s+,\\.\n]"); Multiset<String> col= HashMultiset.create(); for(String string:strings) { col.add(string); } col.elementSet().remove(""); return col.size(); }
-
返回文件行数
public static int lineCounting(BufferedReader br) throws IOException { int countLine = 0; //用于接收每一行的内容 String line = null; while ((line = br.readLine()) != null) { countLine++; } return countLine; }
-
返回代码行 / 空行 / 注释行的数目
public static int[] complexDataCounting(BufferedReader br) throws IOException { //用于存储代码行、空行、注释行的数目 int[] data = {0, 0, 0}; //用于接收每一行的内容 String line = null; //空白行正则表达式 String regexContainNull = "\\s*"; Pattern english = Pattern.compile("[a-zA-z]"); //用于标明下一行是不是注释行 boolean comment = false; while ((line = br.readLine()) != null) { line = line.trim(); if (line.matches(regexContainNull) || line.equals("{") || line.equals("}")) { data[1]++; } else if (line.startsWith("/*") && !line.endsWith("*/")) { data[2]++; comment = true; } else if (line.startsWith("/*") && line.endsWith("*/")) { data[2]++; } else if (true == comment) { data[2]++; if (line.endsWith("*/")) comment = false; } else if (line.startsWith("//") || line.contains("//") ) { data[2]++; if(english.matcher(line).find() && line.contains("}")&& line.contains("//")) data[0]++; } else { data[0]++; } } return data; //0号元素为代码行,1号元素为空行,2号元素为注释行 }
-
递归处理目录
/** * 遍历文件夹下的所有目录,包括子目录 * @param path * @param list */ public static void recursive(String path, List<String> list) { File file = new File(path); //如果文件存在 if (file.exists()) { File[] files = file.listFiles(); if (files != null) { for (File file2 : files) { if(file2.isDirectory()){ recursive(file2.getAbsolutePath(),list); }else if (file2.isFile()){ list.add(file2.getAbsolutePath()); } } } } } /** * 遍历当前目录,不包括子目录 * @param path * @param list */ public static void recursiveNotContainSub(String path, List<String> list){ File file = new File(path); //如果文件存在 if (file.exists()) { File[] files = file.listFiles(); if (files != null) { for (File file2 : files) { if (file2.isFile()){ list.add(file2.getAbsolutePath()); } } } } }
-
通配符匹配,匹配符合条件的文件
/** * 通配符匹配 * list为待处理的文件路径集合,pattern为通配符 * @param list,pattern * @return 匹配后的结果 */ public static List<String> wildcardMatching(List<String> list ,String pattern) { List<String> result = new ArrayList<String>(); if (list.isEmpty()){ return null; }else{ if(pattern!=null){ String[] character = pattern.split("\\."); //如果pattern只有单独一个*,不用匹配 if(pattern.length()==1 && pattern.equals("*")){ return list; } //遍历文件名字匹配通配符 for (String string :list){ //获取文件名 String[] fileNames = string.split("\\\\"); //如果文件名没有后缀,跳出继续遍历下一个 if(!fileNames[fileNames.length-1].contains(".")) break; String fileName = fileNames[fileNames.length-1]; //文件名再次分割 String[] names = fileName.split("\\."); //开始匹配通配符 //文件名开头含有* if (character[0].startsWith("*")){ //如果文件名前缀只有一个* if(character[0].length() == 1){ if(names[1].equals(character[1])) result.add(string); }else{ String[] name1 = character[0].split("\\*"); if(names[0].endsWith(name1[1]) && names[1].equals(character[1])){ result.add(string); } } } //文件名结尾含有* else if(character[0].endsWith("*")){ String[] name1 = character[0].split("\\*"); if(names[0].startsWith(name1[0]) && names[1].equals(character[1])){ result.add(string); } } //文件后缀名含有*的 else if(character[1].equals("*")){ if (character[0].equals(names[0])) result.add(string); if(character[0].contains("?") && names[0].length() == character[0].length()) result.add(string); } //文件名含有? else if(character[0].contains("?")){ if(names[0].length() == character[0].length() && names[1].equals(character[1])){ result.add(string); } } } }else result = list; } return result; }
五、测试运行
将jar包打包成exe文件后进行测试
-
基本功能
![mark]()
![mark]()
![mark]()
![mark]()
-
扩展功能,新增两个功能同时可以匹配通配符
-
遍历目录下的文件
![mark]()
-
递归遍历目录及子目录,同时使用通配符
![mark]()
![mark]()
-
错误处理(1、单独使用参数“-s”,2、对文件使用参数“-s”,3、单独输入参数或者单独输入文件,4、输入错误的路径)
![mark]()
![mark]()
-
-
高级功能,选取一个文件或者目录进行统计
![mark]()
![mark]()
-
单元测试以及代码覆盖率,对
WcUtils中的功能函数进行单元测试![]()
六、PSP
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 10 | 15 |
| · Estimate | · 估计这个任务需要多少时间 | 270 | 585 |
| Development | 开发 | 190 | 525 |
| · Analysis | · 需求分析 (包括学习新技术) | 40 | 50 |
| · Design Spec | · 生成设计文档 | 20 | 25 |
| · Design Review | · 设计复审 (和同事审核设计文档) | 20 | 40 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
| · Design | · 具体设计 | 20 | 20 |
| · Coding | · 具体编码 | 40 | 300 |
| · Code Review | · 代码复审 | 20 | 50 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 20 | 30 |
| Reporting | 报告 | 40 | 60 |
| · Test Report | · 测试报告 | 20 | 30 |
| · Size Measurement | · 计算工作量 | 10 | 10 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 10 | 20 |
| 合计 | 240 | 600 |
七、项目小结
-
开始个人项目后,在需求分析上遇到了一点小小的困难,纠结于是否有标准,最后明确了是我在进行需求分析,而需求分析的对象不再是客户而是我本身,所以根据自己的需要进行需求分析。
-
开发的时候,整个个人项目中觉得最费时间的地方应该是通配符匹配的功能,目前只能匹配
*和?,考虑每一种可能的情况判断起来比较繁琐。 -
整体上项目所要求的功能包括扩展的以及图形界面都实现了,但是还是存在不足的地方的,为保证每一个功能函数都是独立的且可重复使用的,没有在函数中输出结果,而是返回结果后在main函数中再进行输出,导致main函数比较冗长,应该进行进一步的封装。
-














浙公网安备 33010602011771号