软工实践第二份作业

github地址:(https://github.com/LQ77/personal-project)

一、计划表

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 20 20
· Estimate · 估计这个任务需要多少时间 20 20
Development 开发 490 660
· Analysis · 需求分析 (包括学习新技术) 180 300
· Design Spec · 生成设计文档 10 20
· Design Review · 设计复审 30 20
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 30 20
· Design · 具体设计 60 30
· Coding · 具体编码 90 60
· Code Review · 代码复审 30 60
· Test · 测试(自我测试,修改代码,提交修改) 60 150
Reporting 报告 60 120
· Test Repor · 测试报告 10 30
· Size Measurement · 计算工作量 20 50
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 40
|       | 	合计  |570 |800

二、解题思路

需求分析

  1. 统计文件有多少个字符,包括非数字字母字符
  2. 统计文件的有效行数。
  3. 统计单词的总数,该单词定义为“至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写。”
  4. 统计文件中各单词出现的次数,然后输出频率最高的10个。

思路

我选择使用Java完成课题。
首先,在理解题意后,我先考虑需要几个类、几个对象;而后再考虑类中有几个属性、方法;并且整理好类与类之间的关系。
初步分析后,我确定了几个需要的类:首先是io包内的File类,new出从外部输入的文本的File类型,再使用util包内的Scanner类,对该文本中的内容进行跟踪、遍历等功能。根据课题需求,我拟定了四个方法去实现四个功能(后续由于统计字符和行数各只需一句话,固不分离,将统计单词数和计算词频并输出的方法分别存在CountWords和CountMaxOfWords这两个类并包装在lib包内)。
关键的词频统计部分,我联想到了Java中容器Map,其定义了存储键值key和值value的映射对的方法,将单词定义为key,词频定义为value,对统计和遍历将会有极大的帮助,因此我选择使用Map,并构建TreeMap。

三、设计实现

    String directory = "D:\\java\\031602123\\src";//用于测试的txt文件路径
    File f = new File(directory,args[0]);
    //创建一个File类f,指向test,txt;
    //......
    Scanner input = new Scanner(f)//创建一个Scanner类input指向f;
    //......
    Map<String, Integer> words = new TreeMap<String, Integer>();//创建TreeMap用于词频统计部分;
    //利用while循环遍历txt文件内的每一行内容
    while (input.hasNext()) {
				String line = input.nextLine();
				countOfCharacters += line.length();//计算当前行的字符个数;
				countOfLine ++;//行数加一;
    /*这是两个关键的方法,首先是在while循环内,每次调用一次countWords方法,对每一行统计符合条件的单词数,记录或更新词频;另一个则是在循环结束后,对Map中的数据进行一次遍历,并从中筛选  出符合条件的10组映射;*/
    countOfWords += CountWords.countWords(line, words);  //CountWords内的static方法countWords
    CountMaxOfWords.Sort(words); //CountMaxOfWords内的static方法Sort

四、程序改进

在ecilpse中安装JProfiler插件,并进行观察。
首先先让我们看一下输出的结果:
运行情况:
以及内存的分布:

由于在方法CountWords内对单词是否符合条件的判断需要把String内转换成char[],所以char[]比String多,其次就是int[]与Integer,分别在CountMaxOfWords和TreeMap中使用。同时也能看出,方法CountWords是占用率最多的方法。
在最开始,统计单词数和统计词频我是分开来做的,但是假设有n个单词,我就需要遍历k次,n<=k<=2n次,于是我决定边统计单词数、同时统计词频;然后是查找需要的10组最大数据,假设有n组数据,我则需要先从map中遍历一遍,时间为O(n),一开始我对整个数据进行排序后再去取,效果很不理想,复杂时间达到了O(nlogn),但是我利用了类堆排序的方法,只需要每次判断10组就行,时间仅为O(nlog10),大大优化了运行状况。

五、代码说明

我将展示三个部分的关键代码

Main类

    //利用Scanner类构建的input对文本内容进行跟踪,hasNext()是一个类似指针的结构,将指针指向当前行的下一行,这样每次循环就能一一对应每一行,并做出分析、处理等;
		try(Scanner input = new Scanner(f)){
			int countOfCharacters = 0;
			int countOfLine = 0;
			int countOfWords = 0;
			while (input.hasNext()) {
				String line = input.nextLine();
				countOfCharacters += line.length(); //计算当前行的字符个数
				countOfLine ++;
				countOfWords += CountWords.countWords(line, words); //CountWords内的static方法countWords
			}

CountWords类

流程图:

部分代码如下

    //将内容全部变为小写,并利用正则表达式"[^\\w\\d]+",将本行内容分割成每一个只含数字字母字符的小段;
    String mline = line.toLowerCase();
    String[] words = mline.split("[^\\w\\d]+");
	int wcount = words.length;//用于统计符合条件的单词数;
	//进入循环,首先排除掉长度小于4的字符段,其次对其进行判断是否符合条件、而后统计进Map容器内;
	for(int i=0; i<words.length; i++){
		if(words[i].length() < 4)
			wcount--;
		if(words[i].length() >= 4){
			char[] word = words[i].toCharArray();
			int mcount = 0;
			for(int j=0; j<4; j++){
				if((word[j]>='0') && (word[j]<='9')){
					mcount++;
				}
			}
			//只有经历过上方循环后mcount=0才表示该字符段符合单词条件
			//在Map内记录或更新单词——词频映射对
			if(mcount == 0){
				if(m.containsKey(words[i])){
					int v = (Integer)m.get(words[i]);
					v++;
					m.put(words[i],v);
				}
				else{
					m.put(words[i],1);
				}
			}
			else{
				wcount--;
			}
		}
	}

CountMaxOfWords类

流程图:

部分代码如下

    int s = m.size();
    int[] a = new int[10];
	a[0] = 0;		
	int type = 0;
	String[] wd = new String[10];
    /*利用Map.Entry完成对Map内的数据进行以key为基准的遍历,type为一个标准,界定于10内外;
    当数组a存放了10组数据后,调用选择排序方法bbsort,将最小数据放入a[0],之后对于新的数据比较value与a[0],大于则插入,并调用bbsort;*/
    for(Map.Entry<String, Integer> entry : m.entrySet()){
		String k = entry.getKey();
		int v = entry.getValue();
		if((type < 10)&&(type != s)){
			a[type] = v;
			wd[type] = k;
			type++;
		}
		if((type == s)||(type == 10)){
			if(v >= a[0]){
				a[0] = v;
				wd[0] = k;
			}
			bbsort(a,wd,s);
		}
	}

六、异常处理

各个方法的覆盖率:

做了部分的测试:

  1. 传入空文件,输出结果全为0,符合预期;
  2. 不传入文件,输出:Please input any txt,符合预期;
  3. 输入错误或不存在的文件名,输出:The txt does not exist,符合预期;
  4. 输入单词数小于10的文件,文件内容和输出结果如下,符合预期;

七、总结

在这次课题中,我遇到得最大问题就是最新技术的学习和运用,利用这次暑假,虽然对Java有了一定程度的了解,但是JProfiler以及各种插件的使用、程序的改进、异常的处理等等,都很少去做,这次课题实打实的让我完成了一次关于与Java有关的小项目,体会到了“纸上得来终觉浅”。但是通过这次课题,自己的自学能力和代码的查错、改进能力有了不小的进步。虽然求学、求实之路不易,但是自己也在实践之中受益良多,为之后奠定了基础,也让我抱有期待。
posted @ 2018-09-10 21:48  LQ77  阅读(162)  评论(0编辑  收藏  举报