github项目地址 https://github.com/HHTupup/WordCount.git
项目相关要求
实现一个统计程序,它能正确统计程序文件中的字符数、单词数、行数,以及还具备其他扩展功能,并能够快速地处理多个文件。
具体功能要求:
程序处理用户需求的模式为:
wc.exe [parameter] [file_name]
基本功能表
指令 | 功能描述 | 是否实现 |
---|---|---|
-c | 返回文件的字符数 | 是 |
-w | 返回文件的词的数 | 是 |
-l | 返回文件的行数 | 是 |
-a | 返回更复杂的数据(代码行 / 空行 / 注释行) | 是 |
-s | 递归处理目录下符合条件的文件 | 是 |
支持通配符 | 是 | |
-x | 显示图形界面 | 否 |
PSP
*PSP2.1* | *Personal Software Process Stages* | *预估耗时(分钟)* | *实际耗时(分钟)* |
---|---|---|---|
Planning | 计划 | 30 | 20 |
·Estimate | · 估计这个任务需要多少时间 | 30 | 20 |
Development | 开发 | 620 | 1000 |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 330 |
· Design Spec | · 生成设计文档 | 30 | 60 |
·Design Review | · 设计复审 (和同事审核设计文档) | 15 | 10 |
·Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 5 | 5 |
· Design | · 具体设计 | 60 | 60 |
· Coding | · 具体编码 | 300 | 190 |
· Code Review | · 代码复审 | 30 | 40 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 300 |
Reporting | 报告 | 70 | 60 |
· Test Report | · 测试报告 | 20 | 15 |
· Size Measurement | · 计算工作量 | 30 | 15 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 30 |
合计 | 720 | 1080 |
遇到的困难以及解决办法
- 对编程环境不熟悉
- 这次项目我使用我之前很少接触的JAVA实现,开发过程发现它有许多强大的类,然而我还是有点难以驾驭。
- 开始想使用比较熟悉的c语言,但是觉得代码比较难管理,而且文件操作不如JAVA方便,JAVA有更严格的代码审查,能让我更好管理、检查代码。
- 解决方案:使用万能的搜索引擎,再加上自己的摸索去熟悉JAVA。
关键代码及其说明
WordCount实现类
WordCount类是项目的核心,它实现了wc.exe的字符数统计、单词数统计、行数统计以及其他扩展功能。实现这些功能的关键是File类的方法以及String类的方法。
import java.io.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class WordCounter {
//字符数统计
static void CharCounter(String path) throws IOException {
FileReader fr = new FileReader(path);
BufferedReader br = new BufferedReader(fr);
int ch = br.read();
int count = 0;
while(ch != -1) {
if(ch!=' ' && ch!='\n' &&ch!='\r' && ch!='\t') count++;
ch = br.read();
}
br.close();
System.out.println("字符数:"+count);
}
//单词数统计
static void WordCounter(String path) throws IOException {
FileReader fr = new FileReader(path);
BufferedReader br = new BufferedReader(fr);
//用正则表达式匹配 由英文字母组成的子字符串 的个数
String pattern = "[a-zA-Z]+";
Pattern r = Pattern.compile(pattern);
String line = br.readLine();
int count = 0;
while(line != null) {
Matcher m = r.matcher(line);
while(m.find()) {
count++;
}
line = br.readLine();
}
br.close();
System.out.println("单词数:"+count);
}
//行数统计
static void LineCounter(String path) throws IOException {
FileReader fr = new FileReader(path);
BufferedReader br = new BufferedReader(fr);
String line = br.readLine();
int count = 0;
while(line != null) {
count++;
line = br.readLine();
}
br.close();
System.out.println("行数:"+count);
}
//空行、代码行、注释行统计
static void ComplexLineCounter(String path) throws IOException {
FileReader fr = new FileReader(path);
BufferedReader br = new BufferedReader(fr);
int CommentLineCount = 0, BlankLineCount = 0, CodingLineCount = 0;
boolean in_block = false;
String line = br.readLine();
while(line != null) {
line = line.trim();
if(line.contains("/*")) in_block = true;
//单行注释 或者 在区块注释内
if(line.matches("^[{}]?//[^\\n]*") || in_block) {
CommentLineCount++;
}
//空白行
else if(line.matches("^[\\\\s]*$|^[{}();]$")) {
BlankLineCount++;
}
//其余的是代码行
else {
CodingLineCount++;
}
if(line.contains("*/")) in_block = false;
line = br.readLine();
}
br.close();
System.out.println("代码行数:"+ CodingLineCount);
System.out.println("空白行数:"+ BlankLineCount);
System.out.println("注释行数:"+ CommentLineCount);
}
//分配指令并执行
static void batch(String path, List commands) throws IOException {
File file = new File(path);
System.out.println(file.getPath());
for(String item : commands) {
switch (item) {
case "-s": break;
case "-a": ComplexLineCounter(path); break;
case "-c": CharCounter(path); break;
case "-w": WordCounter(path); break;
case "-l": LineCounter(path); break;
default: return;
}
}
System.out.println("");
}
//从指定文件夹开始 递归批量处理文件
static void TraverseFiles(String path, List commands, Pattern regexp)
throws IOException {
File file = new File(path);
//项目限定的文件类型.c .h .cpp 和 .txt
String type = "[.](c|h|txt|cpp)$";
Pattern pattern = Pattern.compile(type);
if(file.exists()) {//判断文件夹路径是否正确
File[] files = file.listFiles();
//如果文件夹为空 或者 这是个文件
if(null == files || files.length == 0) {
return;
}
//如果这是个文件夹
else {
//遍历子文件
for(File fchild : files) {
//如果是文件夹,获取该文件夹路径,进入文件夹,递归调用
if(fchild.isDirectory()) {
TraverseFiles(fchild.getAbsolutePath(), commands, regexp);
}
//如果是文件,并且是.c/ .cpp/ .h/ .txt文件,则查看
else if(fchild.isFile() && pattern.matcher(fchild.getName()).find()
&& regexp.matcher(fchild.getName()).find()){
batch(fchild.getAbsolutePath(), commands);
}
}
}
}
else {
System.out.println(path+":文件不存在");
}
}
}
主函数
接收参数并分配执行,但是我对JAVA的认识还不够,代码写得不太好,最终if,else用得有点多。
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;
public class app {
public static void main(String[] args) throws IOException {
//默认路径是可执行文件当前路径
String path = "./";
//表示是否有从参数中收集到路径,最多只接收一次
boolean received = false;
//收集到的指令集合
List command = new ArrayList<>();
//开始收集指令和路径
for(String item : args) {
//符合指令的格式 并且 不收集重复的指令
if(item.matches("^-.*")) {
if(item.matches(("^-[awslc]$")) && !command.contains(item)) {
command.add(item);
}
else {
System.out.println("错误:指令'"+item+"'无法识别");
return;
}
}
//其余格式被认为是文件路径
else if(!received){
received = true;
path = item;
}
//输入了多个路径,要提示错误,程序结束
else {
System.out.println("错误:最多只能输入一个路径");
return;
}
}
Pattern pattern = Pattern.compile(".*");
//收集结束后, 对收集数据进行分析
//含有 '*', '?' 被认为是通配符模式,从当前目录开始递归查找
if(path.matches(".*[*?].*")) {
path = path.replace("*",".*");
path = path.replace("?",".?");
pattern = Pattern.compile(path);
WordCounter.TraverseFiles("./", command, pattern);
}
//其余被认为是正常文件路径 即相对路径或绝对路径
else {
File file = new File(path);
//检查路径是否正确,正确则执行指令
if(file.exists()){
//指定目录是文件夹
if(file.isDirectory()) {
if(command.contains("-s")) {
WordCounter.TraverseFiles(path, command, pattern);
}
else {
System.out.println("错误:指定目录是文件夹,请先使用-s 指令查看该目录下适用的文件");
return;
}
}
//指定目录是文件
else {
WordCounter.batch(path, command);
}
}
//不正确路径提示错误,程序结束
else {
System.out.println("错误:未找到指定文件或文件夹");
return;
}
}
}
}
运行测试
项目小结
- 对JAVA有更进一步了解,与C语言相比,JAVA的编程更像是在“拼接组件”。
- 编码时间比预计要短,是因为设计阶段花了更多的时间。下手写代码前先做好设计流程,有一个逻辑清晰的流程图能够有效提高编码效率,降低出现bug的频率。
- 代码写得不是很规范,可读性和扩展性稍差。因为是个人项目就没太注意规划。