个人项目作业
项目github地址
https://github.com/Rabbit-bear/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 文件的代码行数、空行数、注释行数。
PSP
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | ||
| · Estimate | · 估计这个任务需要多少时间 | 1250 | 1370 |
| Development | 开发 | ||
| · Analysis | · 需求分析 (包括学习新技术) | 200 | 120 |
| · Design Spec | · 生成设计文档 | 45 | 30 |
| · Design Review | · 设计复审 (和同事审核设计文档) | 0 | 0 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 30 |
| · Design | · 具体设计 | 100 | 60 |
| · Coding | · 具体编码 | 600 | 670 |
| · Code Review | · 代码复审 | 60 | 60 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 200 | 300 |
| Reporting | 报告 | ||
| · Test Report | · 测试报告 | 60 | 50 |
| · Size Measurement | · 计算工作量 | 5 | 5 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 45 |
| 合计 | 1250 | 1370 |
解题思路描述
- 基本功能
-c:文件长度即是字符数。
-w:利用缓冲流读取每一行数据,遍历每一行的每一个字符,用boolean值作为标识符判断字母字符和其他字符来辨别是否为一个单词,再统计单词数。
-l:缓冲流遍历过程中可以统计行数。
- 扩展功能
-s:函数递归查找文件。将当前文件夹中的符合通配符要求的文件和文件夹整合成一个文件集合,不断递归查找,当递归到文件时进行文件操作。
-a:用缓冲流读取文件内容的每一行,并将每一个的内容进行检测,符合代码行,空行,注释行的便统计总数。
- 高级功能
-x:调动图形界面,图形界面可直接输入文本路径或者使用文本选择器,选择文本,用按钮选择功能,把文本文件和按钮的选择作为参数,传入文本处理函数,再将文本信息输出到图形界面
设计实现过程
- 语言选择:java
- 程序总共1个类
- 基本功能
实现该功能一共需要 2个函数,分别是String HanldFile(File file,LinkedList<String> parameter),main(String[] args)。主函数调用String HanldFile(File file,LinkedList<String> parameter)循环switch匹配功能处理每一个参数,进行选择不同的功能。
-c:(int)file.length():统计字符个数
-w:统计单词个数,根据英文字母后一位是否为字母以外的字符判断,是否为一个单词
-l:统计行数,遍历每一行的时候line++可以记录行数
- 扩展功能
实现以上两个功能共需 7个函数,分别为boolean cheakcode(String line),boolean cheaknotes(String line),boolean cheakempty(String line),boolean Compare(File file1,File file2),void SearchFile(File file,LinkedList<String> parameter),String HanldFile(File file,LinkedList<String> parameter),main(String[] args)。
-s:多线程递归查找文件函数SearchFile(File file,String[] parameter)调用boolean Compare(File file1,File file2)比较输入路径文件名或含通配符的文件名,是否与当前文件夹中的文件匹配,若匹配即加入文件集合,循环递归文件集合的每一个元素,当递归到file是文件时调用String HanldFile(File file,LinkedList<String> parameter)根据输入参数进行文件处理,实现功能。
-a:String HanldFile(File file,LinkedList<String> parameter)中输出更复杂的行数据,利用缓冲流遍历文件内容,每一行进行检测,先调用boolean cheakempty(String line)检测是否为空行,再调用boolean cheakcode(String line)检测是否为代码行,最后再调用boolean cheaknotes(String line)检测是否为注释行。
- 高级功能
实现以上三个功能共需 9个函数,分别为boolean cheakcode(String line),boolean cheaknotes(String line),boolean cheakempty(String line),boolean Compare(File file1,File file2),void SearchFile(File file,LinkedList<String> parameter),String HanldFile(File file,LinkedList<String> parameter),void GUIgetFile(),void printMessage(JPanel panel,String sentence,int count),void start(String[] args),main(String[] args)。
-x:显示图形界面。GUIgetFile()为显示视图函数。该函数内定义了两个窗体,父窗体JFrame包含文本框可输入文件路径,按钮JButton可调用JFileChooser选择文件,获得文件后可进行文件处理,文件信息将打印在子窗体JDialog。
代码说明
- 比较文件名是否符合要求,包括通配符的匹配。将文件名转化为字符数组,一个个检测若出现?则跳过,若出现*,则跳过多位,直到出现下一位与*后相同的字符,继续遍历,否则中断,判定为不符合要去。
public boolean Compare(File file1,File file2) {//比较两文件名是否匹配,包括通配符的判断,file1为输入文件名创建的文件对象,file2为真实存在文件
boolean same = true;//返回值
int i,m;//i作为输入文件名转化成字符数组的下标,m作为真实存在文件的文件名转化成字符数组的下标
char[] file1Name = file1.getName().toCharArray();
char[] file2Name = file2.getName().toCharArray();
for(i=0,m=0;m<file1Name.length&&same&&i<file2Name.length;i++,m++) {
if(file1Name[m]!=file2Name[i]) {//当两文件名出现不同时
if(file1Name[m]!='?'&&file1Name[m]!='*') {//若不为?或者*,既是文件名不符,可以结束函数,返回false
same = false;
}
else if (file1Name[m]=='?') {//出现通配符?,应当跳过该位置差异,继续遍历
if(m==file1Name.length-1&&i!=file2Name.length-1) {
same = false;
}
continue;
}
else if (file1Name[m]=='*') {//出现通配符*,应当跳过多个位置差异,再继续遍历
if(i==file1Name.length-1)break;//若刚好是最后一位,即可跳出循环
for(m = i+1;m<file1Name.length-1&&i<file2Name.length;i++) {//跳出多个差异过程
if(file2Name[i]==file1Name[m]) {//出现相同字符,即跳过差异过程须结束,回归第一层循环
break;
}
}
}
}
}
return same;
}
- 查找文件。根据是否存在-s,判断是否进行递归查找操作。
public void SearchFile(File file,LinkedList<String> parameter) {//查找文件
//记录是否递归查找
boolean isrecurrence = false;
if(parameter.getFirst().equals("-s")) {
parameter.removeFirst();
isrecurrence = true;
}
if(file.isFile()) {//判断该文件对象是否为文件
Thread thread = new Thread() {//多线程处理文件
public void run() {
HanldFile(file, parameter);
}
};
thread.start();
}
else {
if(file.isDirectory()) {//判断该文件对象是否为文件夹
File[] fs = file.listFiles();//获取当前文件下的所有文件
for (File file2 : fs) {
if(!isrecurrence&&file2.isDirectory())continue;//不递归查找,则跳过文件夹
SearchFile(file2, parameter);
}
}else {//若不为文件夹,也不是实际文件时
File[] fs = new File(file.getParent()).listFiles();//获取当前文件路径下的所有文件
LinkedList<File> newFolder = new LinkedList<>();
for (File f : fs) {//遍历文件夹,建立适合通配符的文件集合
if(Compare(file,f)||f.isDirectory()) {//若该文件符合文件名要求或该文件为文件夹,即加入文件集合
if(!isrecurrence&&f.isDirectory())continue;//不递归查找,则跳过文件夹
newFolder.add(f);
}
}
for (File f : newFolder) {//循环调用该函数
if(f.isDirectory())//若为文件夹须进行文件名处理
SearchFile(new File(f.getAbsolutePath()+"/"+file.getName()),parameter);
else {
SearchFile(f, parameter);
}
}
}
}
}
- 文件处理函数。先进行文件处理,根据输入参数,进行输出。
public String HanldFile(File file,LinkedList<String> parameter) {//统计更复杂的数据(代码行 / 空行 / 注释行)
int AllLine = 0;//统计总代码行数
int codeline = 0;//统计代码行
int emptyline = 0;//统计空行
int notesline = 0;//统计注释行
int CharAmount = (int)file.length();//字符数
int word = 0;
String line;
if(parameter.getFirst().equals("-s")||!file.isFile()) {//处理多个文件
SearchFile(file, parameter);
return null;
}
String Output = String.format("文件:%s 的相关信息:%n",file.getAbsoluteFile());//构造输入信息
try (
FileReader fReader = new FileReader(file);
BufferedReader bufferedReader = new BufferedReader(fReader);
){
while((line=bufferedReader.readLine())!=null) {//检测读取文本是否应当结束
int count = 0;//记录每一行的单词数
boolean notfound = true;//当前遍历字符不是英文的标识
char[] sentence = line.toCharArray();
for(int i=0;i<sentence.length;i++) {
//当前字符是字母的标识
boolean cheakletters = (('a'<=sentence[i]&&sentence[i]<='z')||('A'<=sentence[i]&&sentence[i]<='Z'));
if(cheakletters&¬found) {//读取到单词第一个字母
count++;
notfound = false;
}
else if((!cheakletters)&&(!notfound)) {//读取单词的下一个非字母字符
notfound = true;
}
}
word += count;
if(cheakempty(line)) {//检测是否空行
emptyline++;
}
else if(cheakcode(line)) {//若不为空行,检测是否代码行
codeline++;
}
else if(cheaknotes(line)) {//若不为空行且不为代码行,检测是否含有注释行,若含有即可判断为注释行
notesline++;
}
AllLine++;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
System.out.println("找不到文件");
}catch (IOException e) {
e.printStackTrace();
System.out.println("打开文件失败");
}
for(int i=0;i<parameter.size();i++) {
switch (parameter.get(i)) {
case "-c": //返回文件 file.c 的字符数
Output += String.format("字符数:%d ",CharAmount);
break;
case "-w": //返回文件 file.c 的单词数
Output += String.format("单词数:%d ",word);
break;
case "-l": //返回文件 file.c 的行数
Output += String.format("行数:%d ",AllLine);
break;
case "-a"://返回更复杂的数据(代码行 / 空行 / 注释行)
Output += String.format("代码行:%d 空行:%d 注释行:%d%n",codeline,emptyline,notesline);
break;
default :
System.out.println("参数输入有误,无法提供信息");
}
}
Message.add(Output);//添加信息
System.out.println(Output);//输出总需求信息
return Output;
}
测试运行
使用普通java文件单独测试所有功能(包括-c,-w,-l,-a)

使用普通java文件整合测试所有功能

使用普通c文件整合测试所有功能

使用普通cpp文件整合测试所有功能

测试程序当前目录下的普通java文件

递归测试



通配符测试


无参数直接打开exe

图形界面操作

点击右侧的按钮
将出现文件选择界面

选择文件后,文件路径将自动显示在文本输入框中

选择其中功能
点击
即可开始查找功能,并自动显示查找信息

点击
即可查看查询记录
测试递归查找文件夹



错误提示
文件路径为空提示

未选择功能提示

文件不可递归提示

项目小结
1.程序从命令行接受到参数后,一次把所有运算完成,再根据参数输出所需的功能,在多参数的条件下比模块化一个函数处理一个功能效率高。
2.在输出文本信息的函数内,用一个String变量整合所有的参数所需的信息再输出,可解决多线程带来的输出混乱。
3.java中自带很多处理字符串的api,可以在这个方向上优化代码长度。

浙公网安备 33010602011771号