WordCount by Java

软测第二周作业

该项目github地址如下:

https://github.com/YuQiao0303/WordCount

一、概述

项目WordCount的需求可以概括为:对程序设计语言源文件统计字符数、单词数、行数,统计结果以指定格式输出到默认文件中,以及其他扩展功能,并能够快速地处理多个文件。

具体来说,需求可参见网址:

http://www.cnblogs.com/ningjing-zhiyuan/p/8563562.html

注意,这里认为如果行内有一个非“{”、“}”或“;”且非空格tab等的字符,认为是代码行。

该项目的PSP表格如下:

二、解题思路

刚拿到项目时,感到总体不难,但是需要攻克的技术点很多很杂,算法逻辑可能也比较绕。

具体来说,需要重点解决下面几个大问题:

1.捡起封尘已久的java及MyEclipse基本用法(如何创建项目,String如何使用等)

2.如何读写文件

采用了java读写文件1的基本方法,配合以java读写文件2中使用的异常类,轻松完成功能。

其中用到了如下几个类:

import java.io.File;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;

3.如何递归找到给定文件及其子目录下的所有文件

采用了递归得到所有文件名中的方法。

File类自带两个很好用的方法:

list() 方法:得到路径下所有文件名,不含路径;

&

listFiles()方法:得到路径下所有文件名,含绝对路径。

通过递归调用这两个方法,即可轻松实现要求。

4.如何提取文件后缀名

采用了java通过文件名判断文件类型中的方法。直接找到文件名中最后一个“.”的坐标,然后取子串即可。

fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length()).toLowerCase();

查到的这一资料让我了解了java中String类的substring方法,之后经常用到。

5.如何判断某一行属于代码行/空行还是注释行

起初,我认为在给出了清晰的判定标准后,用很多判定分支即可达到目的。当我为此方法设计了一个小时后,我感到这样的算法,实在太复杂难懂容易错。

后来听到室友偶尔提了一句“好像判断代码行注释行,可以用正则表达式”。

于是百度正则表达式。在菜鸟教程-java正则表达式中简要学习了正则表达式的相关知识。大体而言,是用于匹配符合一定格式的字符串。

有了这个工具,判断的算法就清晰多了。具体实现请看后文。

6.如何给主方法传参数,并通过逻辑达到需求

//主方法
public static void main(String[] args)
{ for(int i=0;i<args.length;i++)
{
//......
}
//......
return 0;
}

明白了如何传参,接下来的思路是遍历参数数组,使用switch语句判断每个参数的具体情况。

具体实现见后文。

7.如何完成停用词表功能

在室友推荐下,参考了java 统计每个单词出现的个数

主要用到两个方法:

(1) String 类的split(String reg)方法

该方法将正则表达式reg的内容作为分隔符,将字符串按照分隔符分成好几个部分,各部分存放在一个字符串数组中;

方法返回该字符串数组。

于是将正则表达式置为空格,就可以得到输入文件中的单词数组。:

String str[] = line.split(reg1);

(2) TreeMap类的containsKey(String str))方法

该方法返回一个布尔值,若调用它的TreeMap实例中含有str,则返回true,否则返回false。

TreeMap类的功能远不止于此,但此处用到的就是这个“判断是否在集合中”的方法。

只需提前将停用词表进行分割,得到的所有单词放入一个TreeMap对象;再将之前得到的,输入文件的单词数组中的每一单词,都作为参数,判断containsKey的值,即可知道该单词是否要被停用。

8.如何将java程序打包生成exe

直接参考了如何将java程序打包生成exe的方法。

其中,不会像文中那样使用MANIFEST.MF文件,故用了系统默认的MANIFEST.MF文件,同时手动选择主动类的主方法即可。

9.捡起封尘已久的git用法

重学了廖雪峰老师的git教程

常用的git指令如下:

$ git add *
$ git status
$ git commit -m "commit message"
$ git push origin master

同时学习了commit message的写法

三、程序设计实现过程

由于程序功能不复杂,且参数均是从控制台传给主方法,故只用了一个类。该类类图如下:

下面是各个属性和方法的说明(全部是public static):

属性说明

	String inputFile;		//用户输入的文件路径
String outputFile; //输出信息的文件名
String stopList; //停用词表 int chars; //字符数
int words; //单词数
int lines; //行数
int codeLines; //代码行数
int empLines; //空行数
int comLines; //注释行数 boolean needC; //输入参数中是否有“-c”
boolean needW; //输入参数中是否有“-w”
boolean needL; //输入参数中是否有“-l”
boolean needO; //输入参数中是否有“-o” boolean needS; //输入参数中是否有“-s”
boolean needA; //输入参数中是否有“-a”
boolean needE; //输入参数中是否有“-e”

方法说明

	String [] getFileName(String path)

返回 指定路径文件夹path中所有文件名数组(不含路径,含后缀)

	 void getAllFileName(String path,ArrayList<File> fileName)

递归得到 指定路径文件夹path 及其子文件夹中 所有文件名(含绝对路径,含后缀),结果存入fileName

	 void s(ArrayList<String> fileNames)

递归得到 指定路径文件夹(由类的static属性inputFile得到) 及其子文件夹中 所有符合用户要求的(也就是符合后缀名的)文件名(含绝对路径,含后缀),结果存入fileNames。

该方法会调用getAllFileName和getFileName方法。

	 void getBasicInfo(String fileName)

统计指定文件FileName的基本数据:字符数、单词数、行数,将其写入类的static属性chars/words/lines中(不带停用词表)

	 void getStopList(ArrayList<String> wordsIgnored)

得到指定停用词表stoplist中的单词,放在数组wordsIgnored中

	 void getBasicInfoWithSL(String fileName)

统计指定文件fileName的基本数据:字符数、单词数、行数,将其写入类的static属性chars/words/lines中(使用停用词表)

	 void getComInfo(String fileName)

统计指定文件fileName的复杂数据:代码行数、注释行数、空行数,将其写入类的static属性codeLines/comLines/empLines中

	 void main(String[] args)

主方法,整合所有功能。

四、代码说明

停用词表:

line是已经读取的输入文件的一行内容;

tm是已经放入了停用词的TreeMap实例。

将本行所有单词分割存储在str[]中。

遍历str[],如果改单词不在停用词表中,则单词数自增。

            	String reg1 = "\\s+|,+";   //用空格、tab或逗号来分割单词
//String regEmp = "\\s+";
//将读取的文本进行分割
String str[] = line.split(reg1); for(String s: str)
{
if(!s.equals(""))
{ //判断tm即stopList中中是否已经存在该单词,如果不存在则单词数加一;若存在则不变
if(!tm.containsKey(s)) //tm中是否包含该单词
{
//System.out.println(s);
words++;
}
} }

代码行/注释行/空行判断

    	//_________________初始化和变量声明____________________
codeLines=0;
empLines=0;
comLines=0;
int comFlag=0; //每遇到一个“/*”则+1,每遇到一个“*/”则减一;为正说明正处于多行注释之中
String regCom = "(\\s*)(\\{|\\})?(\\s*)(//|/\\*)([\\s\\S]*)"; //单行注释行或多行注释起始行的正则表达式
String regEmp ="(\\s*)(\\{|\\}|;)?(\\s*)"; //空行的正则表达式

这段逻辑主要通过正则表达式进行基本判断;通过comFlag的变化,来判断多行注释。

下面这段代码完成了,统计字符串s中“/”和“/”的功能。

                        int i=0;

            			while(i<s.length())
{
if(s.indexOf("/*",i)==i)
{
comFlag++; i+=2;
}
else if(s.indexOf("*/",i)==i)
{
comFlag--;
//System.out.print("i;m here");
//System.out.print(s.indexOf("*/",i));
//System.out.print(i);
i+=2;
}
else i++; }

篇幅有限,完整函数请参考本篇开头的github网址。

s()函数中,处理各种形式输入参数

我们将用户输入的路径分为几种情况:

*.c 相对路径,无斜杠

src*.c 相对路径,有反斜杠

D:*.c 绝对路径,有反斜杠

src/*.c 相对路径,有正斜杠

D:/*.c 绝对路径,有反斜杠

测试发现,如果是无斜杠或有正斜杠的情况,main收到的参数将是该路径下,所有符合.c后缀名的文件名;若是含反斜杠,main收到的参数将原封不动的保留字符'*',但会将“/”自动变为“\”

我们的s()方法:

void s(ArrayList<String> fileNames)

目标是,得到所有符合用户要求的文件名(含绝对路径,含后缀),存在fileNames中。

采用的方法是调用getAllFileName()方法。

getAllFileName(String path,ArrayList<File> fileName)

getAllFileName()会递归得到path路径下的所有文件和子文件夹中的文件名,存在fileName中。

现在的问题就是如何在调用getAllFileName前,得到正确的path参数。

我们直接把inputFile分成含""和不含""的:

若含,则取斜杠之前的子串作为path;

若不含,则取path为“.”,也就是当前路径。

		//如果 *.c 前面还有带有\的路径
if(inputFile.indexOf('\\')>-1)
{
//System.out.println(inputFile);
path=inputFile.substring(0,inputFile.lastIndexOf('\\'));
} //如果参数是 *.c
else path=".";

之后再调用getallfileNames即可。

主函数参数判断

这段代码完成类的static属性赋值的任务。

由于对用户输入有要求,我们直接认为:

o 参数之后输入的字符串,就是outputFile;

-e 之后输入的字符串,就是stopList;

如果一个参数,不是(-小写字母)的形式,且它前一个参数不是-e或-o,则认为该参数是inputFile。

		inputFile="";
for(int i=0;i<args.length;i++)
{
//System.out.println(args[i]);
//判断参数情况
switch(args[i])
{
case "-c" : needC=true;break;
case "-w" : needW=true;break;
case "-l" : needL=true;break;
case "-o" : needO=true;outputFile=args[i+1];break; case "-s" : needS=true;break;
case "-a" : needA=true;break;
case "-e" : needE=true;stopList=args[i+1];break; default :
if(!args[i-1].equals("-e")&&!args[i-1].equals("-o"))
{ inputFile=args[i];
}
} }

之后,根据参数情况,调用其他方法,将结果写入字符串outputStr;最后将outputStr写入输出文件(根据是否有-o参数,决定是写入默认的restult.txt还是写入用户给定的文件)

五、测试设计过程

测试用例设计

按照白盒测试方法,重点考虑判断分支为风险较大处。

首先绘制程序图。

这时候发现了新问题:对于有一定规模的程序,若要绘制出 包含了所有判断分支 的程序图,一来工作量大,二来方便易用的绘图工具极其难找,三来画出的图将极其复杂,且且占面积极大,难以分析。

很好奇实际商用软件的测试,是如何解决这个问题的。

而对于我们这次的WC,我的做法是,在设计用户输入的参数时,只考虑主函数中大致框架涉及的分支;而使用边缘测试的思想,设计inputFile和stoplist的内容。在绘制程序图方面,参考了王宇轩同学的画法

绘制的程序图如下:(所有判定节点向左为yes,向右为no)

可以看到,只需要3个用例,即可穷尽每个分支,达到判定/条件覆盖的要求。



红线是后文中的测试1,绿线是测试2,蓝线是测试3.

具体涉及的测试用例情况如下:(预期结果与实际运行结果完全相同,请看后面的图)

编号 输入参数
1 wc.exe -s -c -a -w D:\testWC\WC*.c -e stoplist.txt -o D:\testWC\output.txt
2 wc.exe -s -l src*.c
3 wc.exe -a input1.c

此外,程序图的环复杂度为9,理论上需要9个独立的测试用例来覆盖每种条件的线性组合。与此同时,考虑不同的文件名输入方式(相对路径,绝对路径)等,设计了剩下7个用例:

编号 输入参数
4 wc.exe -c input1.c
5 wc.exe -w input1.c
6 wc.exe -l input1.c
7 wc.exe -c D:\testWC\input1.c -o D:\testWC\output.txt
8 wc.exe -a D:\testWC\input1.c
9 wc.exe -l -s *.c
10 wc.exe -w D:/testWC/input1.c -e D:/testWC/stoplist.txt

测试用的输入文件input1.c 和 sub1\input.2 和 sub1\sub2\input3.c的内容均如下:

测试用的停用词表stoplist.txt 内容如下:

测试脚本

直接采用以上10个测试用例,得到测试脚本test.txt内容如下:

wc.exe -s -c -a -w D:\testWC\WC\*.c -e stoplist.txt -o D:\testWC\output.txt
wc.exe -s -l sub1\*.c
wc.exe -a input1.c wc.exe -c input1.c
wc.exe -w input1.c
wc.exe -l input1.c
wc.exe -c D:\testWC\input1.c -o D:\testWC\output.txt
wc.exe -a D:\testWC\input1.c
wc.exe -l -s *.c
wc.exe -w D:/testWC/input1.c -e D:/testWC/stoplist.txt

注意:由于该脚本测试了输入为绝对路径的情况,故在其他电脑上不可直接运行,上传到GitHub上的脚本是全部采用相对路径的。即:

wc.exe -s -c -a -w *.c -e stoplist.txt -o output.txt
wc.exe -s -l sub1\*.c
wc.exe -a input1.c wc.exe -c input1.c
wc.exe -w input1.c
wc.exe -l input1.c
wc.exe -c input1.c -o output.txt
wc.exe -a input1.c
wc.exe -l -s *.c
wc.exe -w input1.c -e stoplist.txt

测试结果

测试结果如下:

全部与预期情况相同。通过测试。

测试评价

本次设计的设计用例,从主方法的大框架上,可以达到修正的判定/条件覆盖的要求;对于被统计文件的内容设计,则采用了边缘测试的思想,重点测试了除主方法外其他方法中,判定表达式的边缘情况。

总体来说比较满意。

六、参考文献连接:

java读写文件1

java读写文件2

递归得到所有文件名

java通过文件名判断文件类型

菜鸟教程-java正则表达式

菜鸟教程-java正则表达式

如何将java程序打包生成exe

廖雪峰老师的git教程

commit message的写法

Windows bat脚本如何运行

Written with StackEdit.

WordCount by Java的更多相关文章

  1. WordCount(java)

    github项目链接 https://gitee.com/huwenli/Wc.git 1.项目简介 WordCount的需求可以概括为:对程序设计语言源文件统计字符数.单词数.行数,统计结果以指定格 ...

  2. WordCount(JAVA实现)

    201631103228,201631101227 1.项目需求 对程序设计语言源文件统计字符数.单词数.行数,统计结果以指定格式输出到默认文件中,以及其他扩展功能,并能够快速地处理多个文件. wc. ...

  3. HADOOP :: java.lang.ClassNotFoundException: WordCount

    I am using eclipse to export the jar file of a map-reduce program. When i am run the jar using comma ...

  4. win10+eclipse+hadoop2.7.2+maven+local模式直接通过Run as Java Application运行wordcount

    一.准备工作 (1)Hadoop2.7.2 在linux部署完毕,成功启动dfs和yarn,通过jps查看,进程都存在 (2)安装maven 二.最终效果 在windows系统中,直接通过Run as ...

  5. WordCount基于本地和java的使用

    直接使用hadoop中的wordcount中的jar包进行使用 JAVA实现WordCount import java.io.IOException; import org.apache.hadoop ...

  6. java根据标点英文分词

    最近学习java字符串部分,用正则表达式做了一个简单的统计单词出现次数的小程序,目前只能统计英文. 整个程序包括三个包,分别为output,run,wordcount wordCount包 执行单词统 ...

  7. Wordcount on YARN 一个MapReduce示例

    Hadoop YARN版本:2.2.0 关于hadoop yarn的环境搭建可以参考这篇博文:Hadoop 2.0安装以及不停集群加datanode hadoop hdfs yarn伪分布式运行,有如 ...

  8. hadoop的统计单词程序WordCount提示找不到WordCount类

    按这里的教程: http://www.imooc.com/learn/391 试验时,发现在wordcount的最后一步一直提示如下错误: Exception in thread "main ...

  9. Hadoop入门经典:WordCount

    转:http://blog.csdn.net/jediael_lu/article/details/38705371 以下程序在hadoop1.2.1上测试成功. 本例先将源代码呈现,然后详细说明执行 ...

随机推荐

  1. 【Android开发笔记】杂项

    Android studio shift+enter : start new line Theme 将     <style name="AppBaseTheme" pare ...

  2. 真正理解 git fetch, git pull 以及 FETCH_HEAD(转)

    转自http://www.cnblogs.com/ToDoToTry/p/4095626.html 真正理解 git fetch, git pull 要讲清楚git fetch,git pull,必须 ...

  3. 时序js插件cubism使用

    document http://iwantmyreal.name/blog/2012/09/16/visualising-conair-data-with-cubism-dot-js https:// ...

  4. 自己实现的简单的grid

    12年在第一家公司的时候,有过很长一段时间在前端的使用研究上.一开始的时候使用ExtJs4.0 MVC 来开发前端,觉得里面的风转的组件非常好用,Panel.window.tree等等,简化了对于前端 ...

  5. C#实现正则表达式

    如果想了解正则表达式的基础知识:http://www.cnblogs.com/alvin-niu/p/6430758.html 一.C#中的Regex类 1.在C#中开发正则表达式,首先要引用Syst ...

  6. UVA Live Achrive 4327 Parade (单调队列,dp)

    容易想到dp[i][j]表示在第i行j个路口的开始走最大高兴值. 每次可以向左走,或者向右边走,然后向北走.(或者直接往北) 向左走到,状态转移为dp[i][j] = dp[i][k] + happy ...

  7. Android(java)学习笔记85:使用SQLite的基本流程

  8. iOS Dispatch_sync 阻塞线程的原因

    大家的知道在主队列上使用dispatch_sync(), - (void)testSyncMainThread { dispatch_queue_t main = dispatch_get_main_ ...

  9. 安装CocoaPods遇到的问题 及其解决

    本人也是第一次安装这个 CocoaPods,所以刚开始也是遇到了很多懵逼的问题,今天终于搞定了,就自己总结一下,如有错误敬请指出,谢谢! 由于之前,对于终端命令行,不是很了解,总感觉很麻烦,所以也一直 ...

  10. python_28_dictionary补充

    #update:合并两个字典,如果有交叉就覆盖更新,没有交叉的就创建 info={ 'stu1101':'Liu Guannan', 'stu1102':'Wang Ruipu', 'stu1103' ...