本次作业gitee地址:https://gitee.com/ackary/WordCount
一、项目简介
1.基础功能
基础功能部分主要实现的功能是统计一个程序设计语言源文件的字符数、单词数、行数,之后将统计结果输出到result.txt文件。可执行程序为wc.exe,实现形式如下:
wc.exe -c file.c //返回文件file.c的字符数
wc.exe -w file.c //返回文件file.c的单词数
wc.exe -l file.c //返回文件file.c的代码行数
wc.exe -o result.txt //将统计结果输出到文件result.txt
2.拓展功能
拓展功能部分实现的功能是递归处理目录下符合条件的.c源文件、返回每个文件对应的代码行/空行/注释行、建立一个停用词表stopList,使得在统计.c源文件的单词总数时,不统计该表中的单词。可执行程序为wcc.exe,实现形式如下:
wc.exe -c -w -l -s *.c //递归处理所有的.c源文件,返回对应的字符数,单词数,总行数
wc.exe -a -s *.c //递归处理所有的.c源文件,返回每个文件的“代码行/空行/注释行”
wc.exe -w -s *.c -e stopList.txt //递归处理所有的.c源文件,返回每个文件中除去停用词表中的单词数
二、PSP表格
PSP2.1 |
PSP阶段 |
预估耗时 (分钟) |
实际耗时 (分钟) |
Planning |
计划 |
30 |
70 |
· Estimate |
· 估计这个任务需要多少时间 |
600 |
840 |
Development |
开发 |
500 |
820 |
· Analysis |
· 需求分析 (包括学习新技术) |
100 |
130 |
· Design Spec |
· 生成设计文档 |
||
· Design Review |
· 设计复审 (和同事审核设计文档) |
||
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
30 |
50 |
· Design |
· 具体设计 |
40 |
30 |
· Coding |
· 具体编码 |
400 |
450 |
· Code Review |
· 代码复审 |
30 |
40 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
90 |
150 |
Reporting |
报告 |
30 |
50 |
· Test Report |
· 测试报告 |
||
· Size Measurement |
· 计算工作量 |
30 |
30 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30 |
40 |
合计 |
560 |
940 |
三、解题思路
我实现了本项目的基础功能和拓展功能部分。由于基础功能部分实现起来较为容易,因此我直接将所有代码全部写在一个类basicCount中,按功能划分主要有统计功能模块(由函数wordCount()实现)、结果输出模块(在main()主函数中实现);至于拓展功能,我写了两个类,一个是wordCount主函数类,这个类对输入的命令进行解析,并调用outputCount中的方法,输出相应的结果;另一个是outputCount类,其中的方法实现统计功能,供主函数类调用。
1、基础功能
(1)、统计功能模块
该模块主要实现字符统计、单词统计、行数统计这三个基本功能,算法相对简单,基本思路就是使用Java IO流的知识逐字符的读取源文件file.c,通过readline()方法返回每一行字符的长度就是该行字符的个数;每次读完一行,对应的行数加1;当读取到 “ ,与 空格” 后对应的单词数加1。
(2)、结果输出模块
该模块就是通过获取对应指令然后输出对应的统计结果,‘-c’输出统计的字符总数,‘-w’输出统计的单词总数,‘-l’输出统计的文件总行数,‘-o’指定对应的输出文件。
2、拓展功能
(1)、停用词表的实现
对代码行、注释行、空行的判断这三个拓展功能的实现还是费了不少时间的,因为我需要根据这三种内容行的不同特征识别出相应的类型。这个部分我花了不少时间去网上寻找算法,同时也学习了正则表达式的内容,我实际开发时间与预估时间的差距大概就是在这个地方,总之费了我不少时间。最后找到了正则表达式如何匹配读取内容,详细见代码部分。
(2)、遍历文件目录
在读取用户命令的时候,会遇到-s命令,这时就需要递归检索当前目录下所有后缀为.c的文件。这里我采用的是在指定路径下递归匹配含特定字符的字符串的算法,大致原理如下:找到一个目录下的所有文件,然后再逐一和用户给定的文件名进行匹配,最后把匹配成功的文件名、文件路径存放在一个存储缓冲区里。这样,就实现了对当前目录下相同类型文件的读取。
四、程序设计
1、基础功能
(1)、命令处理及判断
要求程序处理的命令模式为:wc.exe [parameter] [input_file_name],代码如下:
public static void main(String[] args) throws IOException{
basicCount WC=new basicCount();
String inputFile="file.c";
String outputFile="result.txt"; for(int i=0;i<args.length;i++){
if(args[i].endsWith(".c"))
inputFile=args[i];
if(args[i].equals("-o"))
outputFile=args[i+1];
}
WC.wordCount(inputFile); for(int i=0;i<args.length;i++)
{
switch(args[i]){
case "-c":{
System.out.println("字符个数:" + charNum);
break;
}
case "-w":{
System.out.println("单词个数:" + wordNum);
break;
}
case "-l":{
System.out.println("文件总行数:" + lineNum);
break;
}
case "-o":{
String resultTxt="";
for(int j=0;j<args.length;j++){
if(args[j].equals("-c"))
resultTxt=resultTxt+"\r\n"+"字符个数:" + charNum;
if(args[j].equals("-w"))
resultTxt=resultTxt+"\r\n"+"单词个数:" + wordNum;
if(args[j].equals("-l"))
resultTxt=resultTxt+"\r\n"+"文件总行数:" + lineNum;
}
break;
}
}
}
}
(2)、文件读取和计数
public void wordCount(String inputFile) throws IOException{
String lineString = null;
String[] buffer;
File infile=new File(inputFile);
BufferedReader bf = new BufferedReader(new FileReader(infile));
while((lineString=bf.readLine())!=null){
buffer=lineString.split(",| "); //当读取到“ ,与 空格”后,结束赋值
for(int i=0;i<buffer.length;i++){
if(!buffer[i].equals(""))
wordNum++;
}
lineNum++;
charNum+=lineString.length();
}
bf.close();
}
(3)、输出结果到指定文件
File outfile = new File(outputFile);
outfile.createNewFile(); // 创建新文件
BufferedWriter bw = new BufferedWriter(new FileWriter(outfile));
bw.write(resultTxt);
bw.flush(); // 将缓存区内容压入文件
bw.close(); //关闭文件
2、拓展功能
(1)、实现统计功能的函数模块
public static void count(String inputFile,String stopFile) throws IOException {
String lineString = null;
String[] buffer;
String[] buffer1=null;
File infile = new File(inputFile);
BufferedReader bf = new BufferedReader(new FileReader(infile));
if(isStop){
File file=new File(stopFile);
BufferedReader bff = new BufferedReader(new FileReader(file));
while((lineString=bff.readLine())!=null){
buffer1=lineString.split(",| ");
}
bff.close();
} lineString=null;
// 定义匹配每行的正则匹配器
Pattern notePattern = Pattern.compile("((//)|(/\\*+)|((^\\s)*\\*)|((^\\s)*\\*+/))+",
Pattern.MULTILINE + Pattern.DOTALL); // 注释匹配器(匹配单行、多行、文档注释)
Pattern emptyPattern = Pattern.compile("^\\s*$"); // 空白行匹配器(匹配回车、tab键、空格) try {
while ((lineString = bf.readLine()) != null) {
buffer = lineString.split(",| "); //当读取到“ ,与 空格”后,结束赋值
for (int i = 0; i < buffer.length; i++) {
if(isStop) {
if (!buffer[i].equals("")&&!inStopList(buffer[i], buffer1))
wordNum++;
}
}
lineNum++;
charNum += lineString.length(); if (notePattern.matcher(lineString).find()) {
noteNum++;
}
if (emptyPattern.matcher(lineString).find()) {
emptyNum++;
} else {
codeNum++; // 除空白行和注释行外,其余皆为代码行
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bf.close(); // 关闭文件输入流并释放系统资源
} catch (IOException e) {
e.printStackTrace();
}
}
}
(2)、判定停用词和遍历文件模块
/*判断读取内容是否在停用表内,即读取单词是否为停用词*/
public static boolean inStopList(String str,String[] buffer){
int count=0;
for(int i=0;i<buffer.length;i++){
if(str.equals(buffer[i])){
count++;
}
}
if(count>0)
return true;
else
return false;
} /*遍历文件内容*/
public static List<File> getFile(File dir) {
List<File> files = new ArrayList<File>();
File[] subs = dir.listFiles(); //读取当前文件下的所有文件、文件夹
for (File file : subs) {
if (file.isFile() && file.getName().endsWith(endString)) {
files.add(file);
} else if (file.isDirectory())
files.addAll(getFile(file)); //若读取到目录,就对当前目录递归读取
}
return files;
}
(3)、wordCount主函数
public class wordCount {
public static void main(String[] args) throws IOException {
outputCount WC = new outputCount();
String inputFile = "file1.c";
String stopFile = "stopList.txt";
String outputFile = "result.txt"; for (int i = 0; i < args.length; i++) {
if (args[i].endsWith(".c"))
inputFile = args[i];
if (args[i].equals("-o"))
outputFile = args[i + 1];
if (args[i].equals("-e")){
outputCount.isStop=true;
if((i!=(args.length-1))&&args[i+1].endsWith(".txt"))
stopFile=args[i+1];
}
} boolean flag=false;
for(int i=0;i<args.length;i++){
if(args[i].equals("-s"))
flag=true;
}
if(flag){
for (int i = 0; i < args.length; i++) {
if ((args[i].indexOf("*") != -1)) {
outputCount.endString = args[i].substring(args[i].lastIndexOf("*") + 1, args[i].length());
outputCount.getPath = args[i].substring(0, args[i].lastIndexOf("*"));
if (outputCount.getPath.equals(""))
outputCount.getPath = ".";
}
}
File dir = new File(outputCount.getPath);
List<File> files = outputCount.getFile(dir);
for (File file : files) {
outputCount filesWC = new outputCount();
String filePath = file.getAbsolutePath();
filesWC.count(filePath, stopFile);
filesWC.output(args, filePath, outputFile, filesWC);
outputCount.lineNum=outputCount.wordNum=outputCount.charNum=outputCount.codeNum=outputCount.emptyNum=outputCount.noteNum=0;
}
}
else{
WC.count(inputFile, stopFile);
WC.output(args, inputFile, outputFile, WC);
}
}
}
五、代码测试
1、基础功能测试
(1)、字符统计测试
输入:wc.exe –c file.c
期望输出:字符个数:244
实际输出:字符个数:244
分析:符合预期
(2)、单词统计测试
输入:wc.exe –w file.c
期望输出:单词个数:49
实际输出:单词个数:49
分析:符合预期
(3)、行数统计测试
输入:wc.exe –l file.c
期望输出:文件总行数:19
实际输出:文件总行数:19
分析:符合预期
(4)、输出文件测试
输入:wc.exe -c -w -l file.c -o result.txt
期望输出:
字符个数:244
单词个数:49
文件总行数:19
实际输出:
字符个数:244
单词个数:49
文件总行数:19
分析:符合预期
2、拓展功能测试
(1)、停用词表的测试
输入:wcc.exe -w -s file1.c -e stopList.txt
(源文件file1.c中有49个单词,停用词表中有两个相同单词,故除去停用词表的单词,应该输出的单词数为47)
分析:符合预期
(2)、打印“代码行/空行/注释行”的测试
输入:wcc.exe -a file1.c
分析:符合预期
(3)、遍历文件并打印计数结果的测试
输入:wcc.exe -c -w -l -s -a *.c -e stopList.txt
分析:符合预期
六、参考文献链接
使用exe4j将java文件打成exe文件运行详细教程:https://blog.csdn.net/zzzgd_666/article/details/80756430
FileInputStream、InputStreamReader和BufferedReader几种读取文件的区别:https://blog.csdn.net/pkx1993/article/details/80991812
Java中main方法参数String args[]的使用:https://www.cnblogs.com/xy-hong/p/7197725.html
正则表达式详解:https://www.cnblogs.com/crystaltu/p/5883955.html
七、项目总结
总的来说,我个人认为我这次的项目还存在一定的不足之处,我虽然实现了基础功能和拓展功能,但是对于代码框架的架构还有不足,有些功能的实现过程也不够简洁。同时呢,本次的项目让我还是有较大的收获。首先,通过本次作业,我对于Java的知识有了进一步的了解,因为我本身对Java的了解不是很多,而这次的作业我之所以选择用Java语言来编写,最主要还是想要通过实际的编程来提高我对这门语言的了解,结果我还是比较满意的,我确实收获了许多有关Java的知识,特别是Java IO部分的知识,这对于我的学习非常有用;其次呢,通过本次作业,我也对《系统分析与设计》这门课程理解更深了,作为程序员,我们不仅要有良好的编程基础,还要熟练掌握对代码的测试能力,更要有对一个项目的整体把控能力,我认为这次填写的PSP表格就是对于我们有关这方面的很好训练,我也会在今后的项目过程中不断强调自己这样去做,这是我们所需要具备的能力。