java语言实现对程序设计语言源文件统计字符数、单词数、行数及其他拓展功。
本次作业Github项目地址:https://github.com/YiChenglong2018/WordCount
一、项目简介
本项目的需求可以概括为:对程序设计语言源文件统计字符数、单词数、行数,统计结果以指定格式输出到默认文件中,以及其他扩展功能,并能够快速地处理多个文件。
可执行程序命名为:wc.exe,该程序处理用户需求的模式为:
wc.exe [parameter] [input_file_name]
存储统计结果的文件默认为result.txt,放在与wc.exe相同的目录下。
我采用的是老师推荐的Java语言实现。这个项目看似功能比较简单,实则工作量巨大,原因有三:一、对细节的要求比较高,从基本功能到高级功能,大到功能的实现形式,小到命令输入的格式,都有比较严格的要求,必须按照要求来执行的话需要增加一定的工作耗时;二、综合性比较强,因为要涵盖《软件质量与测试》和《构建之法》两本书中涉及的软件工程的内容,要有PSP表格、需求分析、测试用例、博客撰写,所以整个项目开发的过程不单单只是考虑如何去实现功能这一件事情了,这也导致了所花费的时间增多;三、高强度的连续作业与自身编程水平的限制,整个项目的流程要求非常规范,所以一步一步来执行的话需要大块大块连续的时间,然而当前课程任务并不轻松加上这几天正好又是另一门课程的课程设计上交截止时间,故而只能在零零碎碎的时间之中去完成项目设计和编码实现,在一定程度上降低了效率,当然,还有一点就是大二的面向对象与程序设计课程已经结课一年多了,JAVA这门语言过长时间的未使用也导致了我在编程过程中困难重重,经常找不着头绪。还好最终还是坚持下来了,尽管这次作业的完成还存在很多不足,但先这样吧,以后再汲取经验和教训,慢慢学习改进。
附:PSP表格如下

二、解题思路
项目的大体思路应该是比较清晰的,按类划分的话应该分为主函数类和调用函数类,按功能划分的话应该分为命令解析模块、目录检索模块、统计功能模块、结果输出模块(包括存储结果的文档创建部分)以及主函数模块五大模块。下面介绍一下各模块的基本功能和实现算法:
1、命令解析模块
命令解析就是要顺利地读取并且识别输入的对应命令参数(包括-c -w -l -a -e -o等等),从而决定程序应该运行的对应功能。
在程序执行的过程中,我们按顺序地遍历了当前目录下对应.c文件的字符串内容。当读取到 '-'这个命令符号前缀时 ,我们能知道它将会和下一个字符一起构成一个操作指令,于是我们通过对下一个字符的读取就能够决定这个命令符号具体代表了什么含义。这里分几种情况:若下一个字符是 'c'、 'w'、'l'之类的基本功能参数,则它后面跟的应该是一个*.c文件,实现的是对文件内容的统计功能;若下一个字符是 'o'、'e'之类的拓展功能参数,那么它会紧跟着一个txt文件,实现的是对文件内容停用词检索与执行结果输出到文件的操作功能;也存在下一个字符是程序未定义的参数字符如‘m’、'n'等,这时程序应当是无法识别这类命令参数的,不会执行任何操作,但一般也不会报错。
2、目录检索模块
在读取用户命令的时候,我们会遇到-s命令,这时就需要递归检索当前目录下所有后缀为.c的文件。这里我采用的是在指定路径下递归匹配含特定字符的字符串的算法,它的大致原理如下:找到一个目录下的所有文件,然后再逐一和用户给定的文件名进行匹配,最后把匹配成功的文件名、文件路径存放在一个存储缓冲区里。这样,就实现了对当前目录下相同类型文件的读取。
3、统计功能模块
统计功能模块是整个项目的核心,在执行统计功能之前,要先进行对停用词表的读取工作,也就是说我们要先打开停用词表读取所有的停用词,然后将这些停用词存储下来以便与读取的.c文件单词对照。接着就是字符统计、单词统计、行数统计这三个基本功能的实现了,这个算法比较简单,基本思想就是逐字符读取一遍文件,每次读取,字符数+1;字符由可显示字符变为不可显示字符,单词数+1;读到 '\n' ,行数+1。接下来对代码行、注释行、空行的判断这三个拓展功能才是本次编程的重难点,因为需要根据这三种内容行的不同特征识别出相应的类型。这里我花了一点时间去网上寻找算法,最终发现还是一个正则表达式读取内容匹配的过程:匹配空行的正则表达式或则判断该行只有一个字符,则当前行为空行,匹配/*,如果存在,注释行++,标志进入注释行,继续匹配*/,如果不存在,注释行++,记下总共多少行,遇到*/,注释行++,标志结束注释行,再匹配单行注释,其他的是代码行,最后计算注释的时候要减去只有/*而没有*/的部分。如此,就实现了代码行、注释行与空行的类型判定。
4、结果输出模块
结果输出模块是整个项目相对轻松的一块,只有通过'-o'命令改变默认的结果文件输出路径这一种情况,其余的都可以按照以往的编程经验该显示的显示,该追踪记录的追踪记录。
5、主函数模块
主函数类可以调用功能函数类的参数,从控制台接收用户输入的指令,然后将这些指令组合成完整字符串再调用WordCount方法进行处理,实现程序的各项功能。
参考资料寻找途径:相关编程书籍(如《面向对象与程序设计》)、互联网(如度娘、必应)、大佬们的帮助(提供的解题思路与算法)。
三、程序设计
1、相关定义
上述模块涉及的部分函数体及参数定义如下:



2、类与方法说明
根据功能模块的划分,整个程序应当分为两个类主函数类(CountMain)和功能函数类(WordCount),主函数类通过调用功能函数类的参数,从控制台接收用户输入的指令,然后将这些指令组合成完整字符串再调用WordCount方法进行处理,从而实现程序的功能。所以主函数类只有main函数一个函数。而功能函数类则包含命令解析函数(Command)、统计功能函数(wc)、目录检索函数(List)以及停用词判断(inStop)四个函数,它们之间并列分属于不同的功能模块,共同实现了程序的整体功能。
四、代码解析
1、主函数main(String[] args)
public class countMain{
	public static void main(String[] args) throws IOException{
		WordCount WC=new WordCount();
		String inputFile="file.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("-e")){
				WordCount.isStop=true;
				if((i!=(args.length-1))&&args[i+1].endsWith(".txt"))
					stopFile=args[i+1];
			}
			if(args[i].equals("-o"))       /*接收指令-o后,对outputFile执行操作
				if(i!=(args.length-1))
					outputFile=args[i+1];
		}
		/*-s指令判定操作部分*/
		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)){
					WordCount.endString=args[i].substring(args[i].lastIndexOf("*")+1, args[i].length());
					WordCount.getPath=args[i].substring(0,args[i].lastIndexOf("*"));
				   if(WordCount.getPath.equals(""))
					   WordCount.getPath=".";
				}
			}
			File dir = new File(WordCount.getPath); 
			List<File> files = WordCount.getFile(dir); //获取所有的.c文件
			 for (File file : files) { 
	         WordCount ccount=new WordCount();
	         String filePath = file.getAbsolutePath();//获取文件路径
	         ccount.wc(filePath,stopFile);
	        ccount.Command(args, filePath, outputFile, ccount);//调用命令指令函数执行功能
		}   
		}
		else{
			WC.wc(inputFile,stopFile);
			WC.Command(args, inputFile, outputFile, WC);//在非-s的情况下,只处理inputFile
		}
	}
}
2、统计功能函数wc(String inputFile,String stopFile)
public void wc(String inputFile,String stopFile) throws IOException{
		String lineString = null; 
        String[] buff;
        String[] buff1 = null;
        boolean flagNode = false;
        String regx = "^//.*";               
        String regxNodeBegin = "\\s*/\\*.*"; 
        String regxNodeEnd = ".*\\*/\\s*"; //设定相应的判定特征,通过正则表达式的匹配来判断内容行属于代码行、注释行与空行的哪一种
        if(isStop){            //判断是否属于停用词,若属于则不计入单词总个数
        	File dirr=new File(stopFile);
            BufferedReader bff = new BufferedReader(new FileReader(dirr)); 
            while((lineString=bff.readLine())!=null){
              buff1=lineString.split(",| ");
            }
            bff.close();
        }
        lineString = null;
        File dir=new File(inputFile);
        BufferedReader bf = new BufferedReader(new FileReader(dir));    
        while((lineString=bf.readLine())!=null){
        	buff=lineString.split(",| ");      //读取到“,与空格” 后,结束赋值
        	int notSpace=0;
3、结果输出部分
File file =new File("result.txt");
BufferedWriter result = new BufferedWriter(new FileWriter(file,true));
result.write(outPut+"\r\n");
result.flush(); // 将缓存区中的内容存入创建的文件xxx.txt之中
result.close(); // 关闭文件
		/*将输入-o后读取文件统计结果记录到新创建的xxx.txt之中*/		
		File writename = new File(outputFile); 
    	writename.createNewFile(); // 创建新文件  
        BufferedWriter _output = new BufferedWriter(new FileWriter(writename,true));
		for(int i=0;i<args.length;i++){
			if(args[i].equals("-o")){
	            _output.write(outPut+"\r\n");
			}	
		}
		_output.flush(); // 将缓存区中的内容存入创建的文件xxx.txt之中
        _output.close(); // 关闭文件
	}
4、命令解析函数Command(String[] args,String inputFile,String outputFile,WordCount WC)
public void Command(String[] args,String inputFile,String outputFile,WordCount WC)throws IOException{
String outPut="";
		inputFile=inputFile.substring(inputFile.lastIndexOf("\\")+1, inputFile.length());
		for(int i=0;i<args.length;i++){
			if(args[i].equals("-c"))
				outPut=outPut+inputFile+",字符个数:" + charNum+"\r\n";//-c对应字符个数统计
		}
		for(int i=0;i<args.length;i++){
			if(args[i].equals("-w"))
				outPut=outPut+inputFile+",单词个数:" + wordNum+"\r\n";//-w对应字符个数统计
		}
		for(int i=0;i<args.length;i++){
			if(args[i].equals("-l"))
				outPut=outPut+inputFile+",文件总行数:" + lineNum+"\r\n";//-l对应字符个数统计,以上为基础功能部分
		}
		for(int i=0;i<args.length;i++){
			if(args[i].equals("-a"))
				outPut=outPut+inputFile+",代码行数:"+codeNum+",空行行数:"+spaceNum+",注释行数:"+noteNum+"\r\n";//-a对应代码行、空行、注释行统计,以上为拓展功能部分
		}
		System.out.println(outPut);
5、目录文件检索函数List<File> getFile(File dir)
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;  
	    }
五、测试设计
对于程序测试这一块,我一共设计了10个测试用例,其中包括8个相互独立的常规测试用例和2种类型的错误测试用例。具体测试如下:
1. 基本字符测试
输入:–c file.c
期望输出:file.c, 字符数:283
实际输出:file.c, 字符数:283

分析:符合预期输出
2. 行数字符测试
输入:-l char.c
期望输出:file.c,文件总行数:12
实际输出:file.c,文件总行数:12

分析:符合预期输入
3. 基本功能测试
输入:-c -w -l file.c
期望输出:file.c,字符个数:283
file.c,单词个数:40
file.c,文件总行数:12
实际输出:file.c,字符个数:283
file.c,单词个数:40
file.c,文件总行数:12

分析:符合预期输出
4. 扩展功能测试
输入:-s -a *.c -e stoplist.txt -o output.txt
期望输出:file.c,代码行数:0,空行行数:7,注释行数:0
实际输出:file.c,代码行数:0,空行行数:7,注释行数:0


分析:符合预期输出
5. 文件夹遍历测试
输入:C:\Users\Administrator\Desktop\output>wc.exe -s -c -a *.c
期望输出&实际输出:

分析:符合预期输出
6. 停用词功能测试
输入: -w stoptest.c -e stoplist.txt
期望输出:file.c,文件总行数:12
实际输出:file.c,文件总行数:12

分析:符合预期输出
7. 输出文件测试(result.txt):
输入:C:\Users\Administrator\Desktop\output>wc.exe -s -c -w -l -a *.c -e stoplist.txt -o result.txt
期望输出&实际输出:

分析:符合预期输出
8. Stoplist.txt空文件测试:
输入:-s -a -w -c -l *.c -o output.txt -e stoplist.txt
期望输出:同7期望输出
实际输出:程序报错

分析:因为stoplist.txt内容为空,而程序并未考虑这种情况,直接导致整个命令语句得不到结果
结果:将stoplist.txt输入任意内容则显示正确结果。

9. 错误指令测试一(纯未定义命令参数)
输入:-m char.c
期望输出:无期望
实际输出:无任何内容,但也未报错

分析:由于并未涉及命令参数-m,故而程序无法识别对应指令无法执行。
10. 错误指令测试二(含未定义命令参数)
输入:-c -m file.c
期望输出:无期望
实际输出:file.c,字符个数:283

分析:由于命令语句中含有已定义的命令参数-c,故而虽然含有-m,但是程序依然识别了-c并且执行了字符统计功能。
六、参考文献链接
递归匹配含特定字符串的文件算法:https://www.cnblogs.com/loveMoon/p/6182926.html
java代码行、注释行、空行的正则表达式判定算法:http://blog.csdn.net/hkawei/article/details/54970937
java测试用例简介:http://blog.csdn.net/clamaa/article/details/70046101
七、项目总结
总的说来,这次项目确实让我收获了不少东西,一开始因为要求的细节过于繁琐而且时常所以有些情绪化,在项目设计和开发的过程中没有完全静下心来去好好地想一下如何解决这个问题。随着提交时间的逼近才逐渐硬着头皮开始认真去搞这个作业,从最终的结果来看,确实学到了很多有用的东西,如通过通配符匹配遍历目录下相同类型后缀的文件、关于注释行、代码行与空行的正则表达式匹配判定法以及如何从提高一个程序的条件覆盖率去编写测试用例测试程序的性能,这些都是以后走向工作岗位很宝贵、很重要的东西。不过由于时间的仓促,我编写的程序仍存在着很多不尽如人意的地方,相关的测试用例的设计也有一定的缺憾和漏洞,这些都是我在今后学习中要去学习改进的地方。
java语言实现对程序设计语言源文件统计字符数、单词数、行数及其他拓展功。的更多相关文章
- python统计一个文本中重复行数的方法
		
python统计一个文本中重复行数的方法 这篇文章主要介绍了python统计一个文本中重复行数的方法,涉及针对Python中dict对象的使用及相关本文的操作,具有一定的借鉴价值,需要的朋友可以参考下 ...
 - C++统计代码注释行数 & 有效代码行数 & 代码注释公共行 & 函数个数
		
问题来源,在14年的暑假的一次小项目当中遇到了一个这样的问题,要求统计C++代码的注释行数,有效代码行数,代码注释公共行数,以及函数个数. 下面稍微解释一下问题, 1)注释行数:指有注释的行,包括有代 ...
 - Java实现 蓝桥杯VIP 算法训练 统计字符次数
		
算法训练 统计字符次数 时间限制:1.0s 内存限制:512.0MB 输入一个字符串(长度在100以内),统计其中数字字符出现的次数. 样例输入 Ab100cd200 样例输出 6 import ja ...
 - vs2010 vs2013等vs中如何统计整个项目的代码行数
		
在一个大工程中有很多的源文件和头文件,我如何快速统计总行数? ------解决方案--------------------b*[^:b#/]+.*$^b*[^:b#/]+.*$ ctrl + shif ...
 - vs2017 vs2013等vs中如何统计整个项目的代码行数
		
在一个大工程中有很多的源文件和头文件,我如何快速统计总行数? ------解决方案--------------------b*[^:b#/]+.*$^b*[^:b#/]+.*$ ctrl + shif ...
 - (转)vs2010 vs2013等vs中如何统计整个项目的代码行数
		
在一个大工程中有很多的源文件和头文件,我如何快速统计总行数? ------解决方案-------------------- b*[^:b#/]+.*$ ^b*[^:b#/]+.*$ ctrl + sh ...
 - VS2015 中统计整个项目的代码行数
		
在一个大工程中有很多的源文件和头文件,我如何快速统计总行数? ------解决方案--------------------b*[^:b#/]+.*$^b*[^:b#/]+.*$ ctrl + shif ...
 - 统计整个Xcode工程代码行数
		
打开终端,ls 查看目录,用cd命令 定位到工程所在的目录,然后调用以下命名即可把每个源代码文件行数及总数统计出来: find . "(" -name "*.m" ...
 - 统计代码git提交的行数
		
$ git log --author="$(git config --get user.name)" --pretty=tformat: --numstat | gawk '{ a ...
 
随机推荐
- [转帖]两张图看懂GDT、GDTR、LDT、LDTR的关系
			
两张图看懂GDT.GDTR.LDT.LDTR的关系 2018-06-09 18:13:53 Six_666A 阅读数 2044更多 分类专栏: 深入理解linux内核 转自:http://ju.o ...
 - K8S从入门到放弃系列-(1)环境初始化
			
一.系统规划 主机名 IP 组件 k8s-master01 10.10.0.18 etcd.kube-apiserver.kube-controller-manager.kube-schedu ...
 - python学习-28 map函数
			
1. num_1 = [10,2,3,4] def map_test(array): ret = [] for i in num_1: ret.append(i**2) # 列表里每个元素都平方 re ...
 - Django入门(上)
			
一.Web应用程序 1.web应用程序介绍 Web应用程序是一种可以通过Web访问的应用程序,程序的最大好处是用户很容易访问应用程序,用户只需要有浏览器即可,不需要再安装其他软件. 应用程序有两种模式 ...
 - go变量的定义并赋值
			
变量在定义时没有明确的初始化时会赋值为_零值_. 零值是: 数值类型为 `0`, 布尔类型为 `false`, 字符串为 `""`(空字符串). package main impo ...
 - SAS学习笔记44 宏函数
			
类SAS函数的宏函数 该部分函数共5个,其无论是名字.语法.功能都与SAS函数类似,只是在函数名前多了一个“%”.这5个宏函数分别是: %INDEX %LENGTH %SCAN %SUBSTR %UP ...
 - SAS学习笔记16 SAS创建计数(枚举)变量
 - Kafka集群安装及prometheus监控
			
前提 zookeeper安装参考:https://www.cnblogs.com/JustinLau/p/11372782.html 其他安装参考:https://www.cnblogs.com/lu ...
 - Java单例设计模式和多例设计模式
			
单例设计模型 教学视频链接:https://edu.aliyun.com/course/1011 1,private不可以在类外部访问,但可以在内部访问 2,此时Singleton类内部的instan ...
 - MySql外网不能访问设置
			
mysql的root账户,我在连接时通常用的是localhost或127.0.0.1,公司的测试服务器上的mysql也是localhost所以我想访问无法访问,测试暂停. 解决方法如下: 1,修改表, ...