Github项目:https://github.com/mercuriussss/wc
项目要求
wc.exe 是一个常见的工具,它能统计文本文件的字符数、单词数和行数。这个项目要求写一个命令行程序,模仿已有wc.exe 的功能,并加以扩充,给出某程序设计语言源文件的字符数、单词数和行数。
实现一个统计程序,它能正确统计程序文件中的字符数、单词数、行数,以及还具备其他扩展功能,并能够快速地处理多个文件。
- 基本要求
- -c 统计文件字符数(实现)
- -w 统计文件单词数(实现)
- -l 统计文件行数 (实现)
- 扩展要求
- -s 递归处理目录下符合条件的文件 (实现)
- -a 返回文件代码行、空行、注视行数 (实现)
- 高级要求
- -x 程序图形界面化 (未实现)
PSP表格预估
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
| Planning | 计划 | 40 | 60 |
| · Estimate | · 估计这个任务需要多少时间 | 40 | 60 |
| Development | 开发 | 1200 | 1620 |
| · Analysis | · 需求分析 (包括学习新技术) | 120 | 150 |
| · Design Spec | · 生成设计文档 | 40 | 60 |
| · Design Review | · 设计复审 (和同事审核设计文档) | 40 | 55 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 35 | 45 |
| · Design | · 具体设计 | 130 | 160 |
| · Coding | · 具体编码 | 685 | 960 |
| · Code Review | · 代码复审 | 70 | 90 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 80 | 100 |
| Reporting | 报告 | 240 | 400 |
| · Test Report | · 测试报告 | 120 | 220 |
| · Size Measurement | · 计算工作量 | 60 | 80 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 100 |
| 合计 | 1480 | 2080 |
解题思路
根据需求,应将总体分割为“实现功能的函数”和“负责运行的主函数”两个。
设计过程中由于很多个环节都需要用到检验功能,检验文件、检验命令、检验文件夹等等,于是我将其检验功能单独取出来做成一个函数,方便其余各函数调用以及减少代码重复率。
同时,由于递归函数功能跟其他功能相比之下有点特殊,所以我又将其独立成一个函数。
代码说明
Count函数共有6个方法,其中一个为传入参数的构造方法
charNumber、wordNumber、lineNumber 相对应着 “ -c ” “ -w ” “-l ” 三个基本功能
additionlineNumber 则是实现了 “ -a ” 这个扩展功能
runFunc 则是根据输入指令来选择相应功能的运行
package wc;
import com.mysql.jdbc.StringUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class Count {
int charNum ;
int wordNum ;
int lineNum ;
int spacelineNum;
int notelineNum;
int codelineNum;
String[] cms;
String contentLine;
File file;
BufferedReader br;
public Count(String[] commands , File file){
cms = commands.clone();
this.file = file;
System.out.println("文件:"+file.getAbsolutePath());
}
public void charNumber() throws IOException{
charNum = 0;
br = new BufferedReader(new FileReader(file));
while(( contentLine = br.readLine())!=null){
String str = contentLine.replaceAll("\\s","");
charNum += str.length();
}
System.out.println("字符数:"+charNum);
}
public void wordNumber() throws IOException{
wordNum = 0;
br = new BufferedReader(new FileReader(file));
//用空格替换掉非单词的字符,并用StringUtils类将其切割
while(( contentLine = br.readLine())!=null){
String str = contentLine.replaceAll("\\W"," ");
wordNum += StringUtils.split(str," ",true).size();
}
System.out.println("单词数:"+wordNum);
}
public void lineNumber() throws IOException{
lineNum =0;
br = new BufferedReader(new FileReader(file));
while(( contentLine = br.readLine())!=null){
lineNum++;
}
System.out.println("行数:"+lineNum);
}
//统计空白行、代码行和注释行的数目
public void additionlineNumber() throws IOException{
spacelineNum = 0;
notelineNum = 0;
codelineNum = 0;
boolean flag = false;
br = new BufferedReader(new FileReader(file));
while(( contentLine = br.readLine())!=null){
contentLine = contentLine.trim();
if(contentLine.matches("^\\s*$")){
spacelineNum++;
}else if(contentLine.startsWith("/*")&&!contentLine.endsWith("*/")){
notelineNum++;
flag = true;
}else if (flag == true){
notelineNum++;
if(contentLine.endsWith("*/")){
flag = false;
}
}else if(contentLine.startsWith("//")||contentLine.endsWith("//")){
notelineNum++;
}else{
codelineNum++;
}
}
System.out.println("空白行:"+spacelineNum);
System.out.println("注释行:"+notelineNum);
System.out.println("代码行:"+codelineNum);
}
public void runFunc() throws IOException{
for(String command : cms) {
switch (command) {
case "-c":
charNumber();
break;
case "-w":
wordNumber();
break;
case "-l":
lineNumber();
break;
case "-a":
additionlineNumber();
break;
default:
System.out.println("输入功能有误");
break;
}
}
}
}
CheckOut函数,关于检验文件、文件夹、代码文件和指令的功能皆包含在内
package wc;
import java.io.*;
import java.util.Arrays;
public class CheckOut {
String[] cms;
File file;
String[] cmCol = {"-c","-w","-l","-s","-a"}; //指令数组,用于检验输入指令是否正确
public CheckOut(String[] command,File file){
cms = command.clone();
this.file = file;
}
public boolean checkFile() {
if (file.isFile() && file.exists()) {
return true;
} else {
System.out.println("该文件不存在!请检查路径是否输入错误!");
return false;
}
}
public boolean checkCommand(){
for(String cm : cms){
if(!Arrays.asList(cmCol).contains(cm)){
System.out.println("输入命令有误,请重新输入!");
return false;
}
}
return true;
}
public static boolean checkFile(File file){
if(file.isFile()&&file.exists()){
return true;
}else{
return false;
}
}
public static boolean checkFileDir(File file){
if(file.isDirectory()){
if(file.list().length>0){
return true;
}else{
System.out.println("该目录为空目录!");
return false;
}
}else{
return false;
}
}
public static boolean checkCodeFile(File file){
String str = file.getName();
if(str.endsWith(".java")||str.endsWith(".cpp")||str.endsWith(".c")){
return true;
}else{
return false;
}
}
}
RecFile函数,对输入文件夹进行递归处理,查询目录下符合条件的所有代码文件
package wc;
import java.io.File;
import java.io.IOException;
public class RecFile {
public static void RecF(String[] cms,File file) throws IOException{
if(CheckOut.checkFile(file)&&CheckOut.checkCodeFile(file)){
Count count = new Count(cms,file);
count.runFunc();
}else if(file.isDirectory()) {
File[] files = file.listFiles();
for (File fs : files) {
RecF(cms, fs);
}
}
}
}
主函数Main,整个程序的执行函数
package wc;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws IOException{
System.out.println("\n\t 输入 “-c 文件完整路径” 表示查询文件的字符数 ");
System.out.println("\n\t 输入 “-w 文件完整路径” 表示查询文件的词的数目");
System.out.println("\n\t 输入 “-l 文件完整路径” 表示查询文件的行数");
System.out.println("\n\t 输入 “-a 文件完整路径” 表示查询文件的空行、注释行、代码行数");
System.out.println("\n\t 输入 “-s 其他功能指令 文件完整路径” 表示以特定功能查询文件夹下所有代码文件");
System.out.println("\n\t 输入 “-e” 表示退出程序");
System.out.println("\n\t 以上除了退出功能外,其余皆可叠加指令查询,如-c -w -l 文件完整路径");
while(true){
System.out.println("\n请输入相应命令:");
Scanner sc = new Scanner(System.in);
//按空格切割成字符串数组,并将其分为指令组和文件路径
String commands[] = sc.nextLine().toString().split("\\s");
//判断是否为退出指令
if(commands[0].equals("-e")){
break;
}
String cms[] = Arrays.copyOf(commands,commands.length-1);
String filePath = commands[commands.length-1];
File file = new File(filePath);
//递归查询
if(cms[0].equals("-s")) {
//检验输入指令除了‘-s’外,还含有其他功能指令
if(cms.length>1){
String newcms[] = Arrays.copyOfRange(cms,1,cms.length);
if(CheckOut.checkFileDir(file)) {
RecFile.RecF(newcms, file);
}
}else{
System.out.println("输入指令有误,请重新输入!");
}
}
//普通查询
else{
CheckOut co = new CheckOut(cms,file);
if(co.checkCommand()&&co.checkFile()) {
if(CheckOut.checkCodeFile(file)) {
Count count = new Count(cms, file);
count.runFunc();
}else{
System.out.println("该文件不是代码文件,请重新输入!");
}
}else{
continue;
}
}
}
}
}
测试
程序已打包成exe文件并上传至github



代码覆盖率

总结
虽说图形界面没有完成,但还是勉强强将其他功能给实现了。在实现的过程中,由于之前的java知识遗忘掉蛮多的,所以在编程中都是边想边打码边百度的。
其中也碰到不少坑,例如为了单词的正则表达式烦恼了许久,最后还是决定用\\W这种并不算严谨的方式匹配;还有exe4j的坑,由于我在Count类的wordNumber方法里切割单词时,采用的是StringUtils的切割方法,所以要用到工具包,于是在将项目打包成jar时吃了不少亏,最后的解决方法是已导出的、将没有工具包的项目jar跟工具包jar解压到同一个文件,同时让项目jar的MANIFEST.MF文件覆盖掉后者的文件,最后再将其压缩成一个jar文件。
这个项目的不足之处还有不少,例如检验功能并没有能完全独立出来,代码的耦合性有点高、不太易于维护和升级,倘若要改进的话,需先从这两处着手才行。