Github项目地址:https://github.com/454469625/SoftwareEngineering
题目描述
Word Count
1. 实现一个简单而完整的软件工具(源程序特征统计程序)。
2. 进行单元测试、回归测试、效能测试,在实现上述程序的过程中使用相关的工具。
3. 进行个人软件过程(PSP)的实践,逐步记录自己在每个软件工程环节花费的时间。
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 返回更复杂的数据(代码行 / 空行 / 注释行)。(引用他人博客并加以理解,非自己实现)
支持各种文件的通配符(*,?)(同引用他人博客,未能自己实现)
高级功能:
-x 参数。这个参数单独使用。如果命令行有这个参数,则程序会显示图形界面,用户可以通过界面选取单个文件,程序就会显示文件的字符数、行数等全部统计信息。(未完成)
需求举例:
wc.exe -s -a *.c
返回当前目录及子目录中所有*.c 文件的代码行数、空行数、注释行数。
解题思路:
1.学习java的文件IO以及与String相关的java类的使用
2.学习正则表达式,使用正则表达式来匹配空格,注释符号等
3.学习java文件转为exe的方法
主要代码:
1.main函数
package cn.lu.wordcount; import cn.lu.wordcount.CountUtils; import java.io.*; import java.util.ArrayList; import java.util.Scanner; public class Main{ private static boolean isCountchar = false; private static boolean isCountword = false; private static boolean isCountline = false; private static boolean isRecursion = false; private static boolean isCountdiffline = false; //利用arraylist存储符合条件的文件的绝对路径 private static ArrayList<String> fileList = new ArrayList<String>(); private static Scanner scanner; public static void main(String[] args) throws IOException,ArrayIndexOutOfBoundsException{ /*此处在exe中测试出错,因此改用另一种方法,用scanner //此处args似乎越界了也许我应该给它分配一个空间? //args = new String[2]; //运行报错了但是测试可以通过 //默认最后一个参数为路径名 String path = args[args.length-1]; CountUtils countUtils = new CountUtils(); //判断需要执行的功能 for(int i=0; i<args.length-1; i++){ if(args[i].equals("-c")){ isCountchar = true; } if(args[i].equals("-w")){ isCountword = true; } if(args[i].equals("-l")){ isCountline = true; } if(args[i].equals("-s")){ isRecursion = true; } if(args[i].equals("-a")){ isCountdiffline = true; } } */ //方法二,用scanner获取输入 //switch匹配字符串好像只在1.7版本以上才可以 CountUtils countUtils = new CountUtils(); String opera = null; String path = null; scanner = new Scanner(System.in); System.out.println("请输入你要执行的操作:"); opera = scanner.nextLine(); System.out.println("请输入你要操作的文件绝对路径:"); path = scanner.nextLine(); switch (opera) { case "-c": isCountchar = true; break; case "-w": isCountword = true; break; case "-l": isCountline = true; break; case "-s": isRecursion = true; break; case "-a": isCountdiffline = true; break; default: System.out.println("操作不存在."); } //获取目录名 //split 里面的参数是正则表达式,在Java里面用字符串表示正则表达式时, //反斜杠是转义符,表示一个反斜杠时,要在前面加一个斜杠,即 \ 表示一个斜杠。 //所以此处split中的\\表示\用于分割文件路径 //此处的一系列操作是为了获取所给文件路径的目录,即将路径最后的文件名删去 String paths[] = path.split("\\"); StringBuilder sb = new StringBuilder(); for(int i=0; i<paths.length-1; i++){ if(i==paths.length-2){ sb.append(paths[i]); }else{ sb.append(paths[i] + "\"); } } String dirName = sb.toString(); //File(String pathname)通过给定路径名字符串转换为抽象路径名来创建一个新File实例 File file = new File(dirName); if (!file.isDirectory()){ System.out.println("路径错误!"); } String fileName = paths[paths.length - 1]; //对文件名通配符处理 //replaceAll() 方法使用给定的参数 replacement 替换字符串所有匹配给定的正则表达式的子字符串。 //在 Java中,\ 表示:我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义 fileName = fileName.replaceAll("\*","\.+").replaceAll("\?","\."); //若IS_RECURSION,则使用递归获取文件名(包括子目录),否则只获取当前目录下符合条件的文件名 if (isRecursion){ countUtils.getRecursionFiles(dirName, fileName); }else{ countUtils.getFiles(dirName,fileName); } fileList = countUtils.fileList; //遍历fileList,对每一个文件使用选择的功能 for (String fList : fileList) { System.out.println("文件路径为:"+fList); if (isCountchar){ countUtils.countChar(fList); } if (isCountword){ countUtils.countWord(fList); } if (isCountline){ countUtils.countLine(fList); } if (isCountdiffline){ countUtils.countDiffline(fList); } } } }
2.工具类
package cn.lu.wordcount; import java.io.*; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 部分引用:https://www.cnblogs.com/yw0219/p/8047938.html,https://www.cnblogs.com/happyOwen/p/9646411.html * */ public class CountUtils { //实现-c功能 利用BufferedReader整行读取统计字符数 public int countChar(String path){ File file = new File(path); BufferedReader br = null; String line; int charCount = 0; try { br = new BufferedReader(new FileReader(file)); while ((line = br.readLine()) != null) { char[] chars = line.toCharArray(); for (int i = 0; i < chars.length; i++) { //Character 类用于对单个字符进行操作 //若不是空格,则字符数加1 if (!Character.isWhitespace(chars[i])) { charCount++; } } } System.out.println("the charCount is:" + charCount); br.close(); } catch (Exception e) { System.out.println(path+"文件名错误"); } return charCount; } //实现-w功能 //统计英文单词数 //先用Bufferedreader整行读取,然后添加到StringBuffer中, //将StringBuffer转化为字符串后,然后将非英文字符替换为空格,再根据空格分割 public int countWord(String path) { BufferedReader br = null; String line; String[] strings; StringBuffer sbf = new StringBuffer(); int wordCount = 0; String reg = "\s+"; try { br = new BufferedReader(new FileReader(path)); while ((line = br.readLine()) != null) { sbf.append(line); } //将不是英文字母的字符替换成空格 String s = sbf.toString().replaceAll("[^A-Za-z]", " "); strings = s.split(reg); wordCount = strings.length; System.out.println("the wordcount is;"+wordCount); br.close(); }catch (Exception e){ System.out.println(path+"文件名错误"); } return wordCount; } //-l 统计总行数 public int countLine(String path) { BufferedReader br = null; String line; int lineCount = 0; try { br = new BufferedReader(new FileReader(path)); while ((line = br.readLine()) != null) { lineCount++; } System.out.println("the lineCount is:"+lineCount); br.close(); }catch (Exception e){ System.out.println(path+"文件名错误"); } return lineCount; } //-a 统计注释行、空行、代码行 public void countDiffline(String path) { int annotationLineCount = 0; int codeLineCount = 0; int spaceLineCount = 0; String line; BufferedReader br = null; //注释匹配器(匹配单行、多行、文档注释) Pattern annotationLinePattern = Pattern.compile("(//)|(/\*)|(^\s*\*)|(^\s*\*+/)"); try { br = new BufferedReader(new FileReader(path)); while ((line = br.readLine()) != null) { if (annotationLinePattern.matcher(line).find()) { //注释行 annotationLineCount++; } else if (line.matches("\s*\p{Graph}?\s*")) { //空行 spaceLineCount++; } else { codeLineCount++; } } System.out.println("the nullLineNum is:" + spaceLineCount); System.out.println("the annotationLineNum is:" + annotationLineCount); System.out.println("the codeLineNum is:" + codeLineCount); br.close(); } catch (Exception e) { System.out.println(path+"文件名错误"); } } //用ArrayList存储符合条件的文件的绝对路径 public ArrayList<String> fileList = new ArrayList<String>(); //递归获取符合条件的文件的绝对路径(在Main.java中已经对fileName进行通配符处理) public void getRecursionFiles(String dirName, String fileName) { File file = new File(dirName); if (file.isDirectory()) { File[] files = file.listFiles(); if (files!=null){ for (File f : files) { //当file1仍然是目录时,递归调用此函数 if (f.isDirectory()) { getRecursionFiles(dirName+"\"+f.getName(),fileName); }else{ //处理后的fileName作为匹配规则,对遍历的文件进行匹配 //pattern 对象是一个正则表达式的编译表示。Pattern 类没有公共构造方法。 // 要创建一个 Pattern 对象,必须首先调用其公共静态编译方法, // 它返回一个 Pattern 对象。该方法接受一个正则表达式作为它的第一个参数。 Pattern pattern = Pattern.compile(fileName); //Matcher 对象是对输入字符串进行解释和匹配操作的引擎。Matcher 也没有公共构造方法。 // 需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象 Matcher m = pattern.matcher(f.getName()); if (m.matches()) { fileList.add(dirName+"\"+f.getName()); } } } } } } //非递归获取符合条件的文件的绝对路径(在Main.java中已对fileName进行通配符处理) //仅获取当前目录下符合条件的文件,并将其绝对路径添加到ArrayList public void getFiles(String dirName, String fileName) { File file = new File(dirName); if (file.isDirectory()) { File[] files = file.listFiles(); if (files != null) { for (File f : files) { if (!f.isDirectory()) { Pattern pattern = Pattern.compile(fileName); Matcher m = pattern.matcher(f.getName()); if (m.matches()) { fileList.add(dirName+"\"+f.getName()); } } } } } } }
测试结果
1.空文件
2.典型的文件
psp
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
30 |
40 |
· Estimate |
· 估计这个任务需要多少时间 |
360 |
480 |
Development |
开发 |
120 |
150 |
· Analysis |
· 需求分析 (包括学习新技术) |
360 |
420 |
· Design Spec |
· 生成设计文档 |
10 |
10 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
10 |
10 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
10 |
30 |
· Design |
· 具体设计 |
40 |
75 |
· Coding |
· 具体编码 |
100 |
150 |
· Code Review |
· 代码复审 |
20 |
30 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
30 |
60 |
Reporting |
报告 |
25 |
25 |
· Test Report |
· 测试报告 |
10 |
10 |
· Size Measurement |
· 计算工作量 |
5 |
5 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
15 |
30 |
合计 |
1055 |
1525 |
总结:由于实践次数少之又少,导致知识学了完全不会用,而且学的知识也有很大的漏洞,在实际设计的时候需要一直百度,即使自己明白怎么做,可是在开发的时候又有各种小问题,就连github的上传都要花半小时重新学习,希望以后自己能提高实践的次数,千里之行始于足下。