Github项目地址:https://github.com/jat0824/wc.git
项目相关要求
wc.exe 是一个常见的工具,它能统计文本文件的字符数、单词数和行数。这个项目要求写一个命令行程序,模仿已有wc.exe 的功能,并加以扩充,给出某程序设计语言源文件的字符数、单词数和行数。实现一个统计程序,它能正确统计程序文件中的字符数、单词数、行数,以及还具备其他扩展功能,并能够快速地处理多个文件。具体功能要求:程序处理用户需求的模式为:
wc.exe [parameter] [file_name]
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 700 | 630 |
Development | 开发 | 640 | 590 |
· Analysis | · 需求分析 (包括学习新技术) | 90 | 90 |
· Design Spec | · 生成设计文档 | 30 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 15 |
· Design | · 具体设计 | 30 | 25 |
· Coding | · 具体编码 | 200 | 180 |
· Code Review | · 代码复审 | 60 | 40 |
· Test | · 测试(自我测试,修改代码,提交修改) | 180 | 180 |
Reporting | 报告 | 120 | 100 |
· Test Report | · 测试报告 | 60 | 60 |
· Size Measurement | · 计算工作量 | 30 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 20 |
合计 | 820 | 720 |
遇到的困难及解决方法
-
问题描述做过的尝试
- 1、关于如何将参数传入main方法的问题
- 2、如何统计词数
- 3、如何判别注释段是否结束
- 4、通配符的处理
- 做过的尝试
-
1、用Scanner类获取参数输入->使用main函数的argv数组获取参数输入
-
2、逐行读取文件,除去特殊字符后用空格分离该行,返回数组长度
-
3、设置了两个变量来记录注释段的开始字符个数和结束字符个数,只有两个变量相等的时候才表示注释段结束
-
4、将通配符按正则表达式的规则替换掉了*和?
-
-
成功解决问题
-
收获:了解了main函数参数的作用,学习并运用了正则表达式,多角度思考问题
设计实现过程
包含以下几个函数:
1、stringcount():实现-c、-l操作:统计文件字符数或行数
2、wordcount():实现-w操作:统计指定文件中词的个数
3、handleall():实现-s操作:递归读取目录下符合条件的文件,加入List
4、moredata () :实现-a操作: 统计代码行/空行/注释行
5、Checkfile () :判断需要处理的是目录还是文件
6、deal () :按正则表达式处理含通配符的文件名
7、main():主方法
实现过程:
在主方法中判断输入的参数并进行相应的操作,-s操作需要先调用Checkfile () 函数,在Checkfile () 函数中调用了deal () 函数规范文件名,然后再调用了handleall()函数处理符合条件的文件。
关键代码
-s、-a操作实现:
在主函数中:首先判断参数是否为-s,然后调用Checkfile()方法判断输入的最后一个参数是目录还是文件,最后通过handleall()方法对符合条件的文件进行后续操作。
// 判断需要处理的是目录还是文件 public static void Checkfile() { int pathlen = filename.get(0).length(); int filelen = 0; String name = null; String file = filename.get(0); // 判断是否为文件名,是否含有通配符 if (file.replaceAll("[^//.^//*^//?]", "").length()==0) { handleall(file, ""); }else { if (file.contains("/")) { String[] n = file.split("/"); name = n[n.length-1]; filelen = name.length(); handleall(file.substring(0, pathlen-filelen), deal(name)); } else { handleall("", deal(file)); } } } // 实现-s操作:递归读取目录下符合条件的文件,加入List public static List<String> handleall(String path, String name) { Matcher m = null; if (path.equals("")) { dir = new File("src"); }else { dir = new File(path); } File[] files = dir.listFiles(); if (files == null) { System.out.println("该目录为空!"); } for (File f : files) { if (f.isFile()) { if (name.equals("")) { filename.add(f.getAbsolutePath()); }else { Pattern p = Pattern.compile(name); m = p.matcher(f.getName()); if (m.matches()) { filename.add(f.getAbsolutePath()); } } }else if (f.isDirectory()) { handleall(f.getAbsolutePath(), name); } } return filename; } // 按正则表达式处理含通配符的文件名 public static String deal(String name) { name = name.replace(".", "#"); name = name.replaceAll("#", "\\."); name = name.replace("*", "#"); name = name.replaceAll("#", ".*"); name = name.replace("?", "#"); name = name.replaceAll("#", ".?"); name = "^" + name + "$"; // System.out.println(name); return name; } // 实现-a操作 public static String moredata(int first) throws IOException { int line = 0; int null_line = 0; // 空行 // 用于匹配注释段的起始(/*)与结束(*/),只有当cp1==cp2时注释段才结束 int cp1 = 0; int cp2 = 0; // 标记整段注释的起始行数 int note = 0; int end = 0; int note_line = 0; // 注释行 int code_line = 0; // 代码行 File files = new File(filename.get(first)); if (files.exists()) { // 判断文件是否存在 BufferedReader br = new BufferedReader(new FileReader(files)); String st = null; while ((st = br.readLine())!=null) { // 如果文件中最后一行为空行则最后一行不算一行 ++line; // 将字符串中的空格、回车、换行符、制表符去掉 String s = st.replaceAll("\\s*|\t|\r|\n", ""); if (s.equals("") || s.equals("{") || s.equals("}")) { // 判断空行用s.equals("") // 注释段中的空行不算在空行数中,算在注释行数中 if (s.equals("") && cp1!=cp2) { ; }else { null_line++; } } else if (s.startsWith("//") || s.startsWith("{//") || s.startsWith("}//")) { note_line++; } else if (s.startsWith("/*")) { cp1++; if (line>note && note!=0) { continue; }else { note = line; } } else if (s.endsWith("*/")) { cp2++; if (line>end) { end = line; }else { continue; } if (cp2 == cp1) { note_line += end-note+1; } } // 代码行=总行数-注释行-空行 code_line = line-note_line-null_line; } br.close(); System.out.println("文件" + filename.get(first) + "的代码行/空行/注释行 分别为: " + code_line + "/" + null_line + "/" + note_line); return code_line + "/" + null_line + "/" + note_line; } else { System.out.println(files.getPath() + " 文件不存在!"); return "no such file!"; } }
-
Checkfile()方法用于判断输入的路径是目录还是文件,再通过handleall()方法将各符合条件的文件绝对路径存入List表中。输入的是目录则递归对该目录下的所有文件进行后续操作,输入的是文件则直接对符合条件的文件进行后续操作(默认的文件路径在src目录下)
-
deal()方法是按正则表达式的规则处理含通配符的文件名,例如:输入的文件名为 *.?d 则经过处理后文件名为 ^.*..?d$
-
- 其中^和$分别表示正则表达式的起始和终止
- . 匹配除"\r\n"之外的任何单个字符; * 表示零次或多次匹配前面的字符或子表达式; ?表示零次或一次匹配前面的字符或子表达式;
-
moredata()方法中实现了对代码行、空行、注释行的统计,其中注释行的统计主要设置了两个int类变量cp1和cp2 对读取的每一行字符串进行判断,是否为"/*"开头,是则cp1++,是否为"*/"结尾,是则cp2++。因此只有当cp1==cp2的时候注释段才结束,这样设计主要针对注释段中含有注释段的特殊情况。并且注释段中的空行算在注释行数不算在空行数。
-
运行结果截图:
-
输入命令:
-
结果输出:
-
测试运行
运行测试单元文件,运行结果截图:
项目小结
在一开始分析需求的时候没有彻底分析清楚需求,导致设计的时候没有考虑全面,在快结束的时候才发现需求没有完全实现。而且有些功能会有些不能解决的情况,例如:-w功能统计词数的时候如果两个词都顶格,分布在相邻两行或者中间隔着若干空行,则该功能会将两个词算为一个词。下次开发的时候不能粗略的按照自己的理解看完需求就马上开始设计,应该认真的看完需求文档之后再进行设计。在代码规范方面,应该在平时写代码的时候就养成好习惯,在需要解释说明的地方写好注释,代码的格式也应该有自己的风格。在开发完后可以给自己的项目写个用户说明文档以便用户使用。