码云项目地址
https://gitee.com/holmec/PersonalProject-Java
PSP表格
PSP2.1 | 个人开发流程 | 预估耗费时间(分钟) | 实际耗费时间(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 15 |
· Estimate | 明确需求和其他相关因素,估计每个阶段的时间成本 | 20 | 15 |
Development | 开发 | 350 | 390 |
· Analysis | 需求分析 (包括学习新技术) | 40 | 35 |
· Design Spec | 生成设计文档 | 20 | 15 |
· Design Review | 设计复审 | 20 | 15 |
· Coding Standard | 代码规范 | 20 | 10 |
· Design | 具体设计 | 30 | 50 |
· Coding | 具体编码 | 120 | 180 |
· Code Review | 代码复审 | 40 | 15 |
· Test | 测试(自我测试,修改代码,提交修改) | 60 | 70 |
Reporting | 报告 | 90 | 90 |
· | 测试报告 | 40 | 40 |
· | 计算工作量 | 20 | 20 |
· | 并提出过程改进计划 | 30 | 30 |
解题思路描述
- 刚看到题目的时候先分析了一下这道题的需求:
- 统计文本的字符数
- 统计合法的单词数
- 统计合法行数
- 统计最多的10个单词及其词频
- 可见要统计首先得先从文件中获取数据,因为考虑到需要统计合法行数,所以我是想按行读取数据,而不是全部读取再来判断换行符之类的。按行读取数据后存储在ArrayList中,以便后续统计时调用,防止多次打开关闭文件造成不必要的异常。
- 统计文本字符数:遍历列表,转换成字符串再转换成字符数组,遍历字符数组,判断是否是合法字符。
- 统计合法单词数:遍历列表,转换成字符串,先判断字符串长度是否大于等于4(如果小于4,那肯定不是单词),通过split方法分割每行字符串。遍历分割后的字符串,定义一个标识符来标识是否合法,判断前四个字符串是否是英文字母,若为合法单词,添加到map里。
- 统计合法行数:遍历列表,转换成字符串,判断字符串长度是否大于0,若大于0则为合法行。
- 统计最多的10个单词及其词频:利用比较器进行排序。
设计实现过程
一、相关类的设计
- File类:读取文件数据,写入数据到文件
- Count类:统计函数的实现
- Main类:其他类和函数的调用
二、相关函数的设计
1. File类:
- readfile函数:从文件中按行读取数据并保存到ArrayList中
- writefile函数:将统计得到的结果写入文件中
2. Count类:
- CountChars函数:统计文本字符数
- CountWords函数:统计文本合法单词数
- CountLine函数:统计文本合法行数
- WordTop函数:统计最多的10个单词及其词频
代码说明
1. CountChars函数:
public int CountChars(){ //统计文本字符数 int count=0; for(int i=0;i<line.size();i++){ //遍历文本数据 String str=line.get(i); for(int j=0;j<str.length();j++){ //遍历每行字符串 char c=str.charAt(j); //转换成字符 if(('A'<=c&&c<='Z')||('a'<=c&&c<='z')||c=='\n'||c=='\r'||c=='\t'){ count++; //若为合法字符则计数 } } } return count; }
2. CountWords函数:
public int CountWords(){ //统计文本合法单词数 int count=0; for(int i=0;i<line.size();i++){ //遍历文本数据 String str=line.get(i); if(str.length()>=4){ //若该行字符串长度大于等于4,则该行可能存在合法单词 String[] words=str.split("[^a-zA-Z0-9]+"); //通过正则表达式匹配分隔符来分割字符串 for (String word:words){ //遍历分割后的字符串数组 int flag=0; //标识符标识是否为合法单词 char[] w=word.toCharArray(); for(int j=0;j<4;j++){ //遍历字符串数组的前四个字符 if(!(('A'<=w[j]&&w[j]<='Z')||('a'<=w[j]&&w[j]<='z'))){ flag=1; break; //若不为英文字母则标识符赋值为1并跳出循环 } } if(flag==0){ //若为合法单词 if(!map.containsKey(word)){ //该单词第一次出现 count++; //单词数加1 map.put(word, 1); //将键、值添加进map中 } else{ int num=map.get(word); //该单词已出现过 map.put(word, ++num); //单词词频加1 } } } } } return count; }
3. CountLine函数:
public int CountLine(){ //统计文本合法行数 int count=0; for(int i=0;i<line.size();i++){ //遍历文本数据 String str=line.get(i); if(str.length()>0){ //若该行字符串长度大于0,则计数 count++; } } return count; }
4. WordTop函数:
public ArrayList WordTop(int count){ //统计最多的10个单词及其词频 ArrayList<Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(map.entrySet()); //定义一个list来存放排序后的单词及其词频 Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() { //重写比较器的排序函数 @Override public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) { if (o1.getValue() == o2.getValue()) { return o1.getKey().compareTo(o2.getKey()); } return o2.getValue() - o1.getValue(); } }); if(count<=10){ //若单词数不大于10,则直接返回排序后的list return list; } else{ //若单词数大于10,则将第10位以后的单词从list中移除再返回list for(int i=list.size();i>=10;i--){ list.remove(i); } return list; } }
单元测试
设计了10个测试点:
- 测试文件不存在
- 测试空白行
- 测试字母大小写是否区别
- 测试长度小于4的行是否合法
- 测试空白文件
- 测试纯中文文件
- 测试无合法单词
- 测试合法单词数不大于10
- 测试合法单词数大于10
- 测试特殊分隔符
测试代码
package Test; import static org.junit.Assert.*; import java.util.ArrayList; import org.junit.Test; import WordCount.Count; import WordCount.File; public class CountTest { String NoExist="D:\\java练习\\PersonalProject-Java\\201621123033\\NoExist.txt"; String test1="D:\\java练习\\PersonalProject-Java\\201621123033\\test1.txt"; String Blank="D:\\java练习\\PersonalProject-Java\\201621123033\\blank.txt"; String Chinese="D:\\java练习\\PersonalProject-Java\\201621123033\\test2.txt"; String NoWord="D:\\java练习\\PersonalProject-Java\\201621123033\\noword.txt"; String more10="D:\\java练习\\PersonalProject-Java\\201621123033\\test3.txt"; @Test public void testreadfile() { File file=new File(); ArrayList<String> line1=new ArrayList<>(); line1=file.readfile(NoExist); boolean test1=(line1.equals(null)); assertEquals(test1,true); } @Test public void testCountChars() { File file=new File(); ArrayList<String> line1=new ArrayList<>(); ArrayList<String> line2=new ArrayList<>(); line1=file.readfile(Chinese); line2=file.readfile(Blank); Count count1=new Count(line1); Count count2=new Count(line2); int CountChars1=count1.CountChars(); int CountChars2=count2.CountChars(); assertEquals(CountChars1,0); assertEquals(CountChars2,0); } @Test public void testCountWords() { File file=new File(); ArrayList<String> line=new ArrayList<>(); line=file.readfile(test1); Count count=new Count(line); int CountWords=count.CountWords(); int CountLine=count.CountLine(); assertEquals(CountWords,5); assertEquals(CountLine,5); } @Test public void testCountLine() { File file=new File(); ArrayList<String> line=new ArrayList<>(); line=file.readfile(test1); Count count=new Count(line); int CountLine=count.CountLine(); assertEquals(CountLine,5); } @Test public void testWordTop() { File file=new File(); ArrayList<String> line=new ArrayList<>(); line=file.readfile(more10); Count count=new Count(line); int CountWords=count.CountWords(); int WordTop=count.WordTop(CountWords).size(); assertEquals(WordTop,10); } }
结果出现了error
检查代码发现
1.CountWords函数没有考虑到字符数小于4的字符串的情况↓
更改后↓
2.还有WordTop函数
更改后↓
3.还有大小写也有问题
更改后↓
4.增加了文件不存在时的提示信息
再次测试
分支覆盖率
效能分析
下了JProfiler,然而并不会用...再研究一下
心得小结
以前写代码的时候都没有太注意代码规范,命名类、函数、变量等都很随意,一开始写倒还好,写到后面就很容易忘记这个类、函数、变量是什么含义。这次按照要求,命名时规范化了,思路清晰了很多,就算不是一次性写完这个程序,后面再继续写的时候也很容易就能回忆起前面的内容。其实这个程序从实现上来说不难,但是后面测试的时候发现了很多逻辑上错误的地方。以前没有使用过测试工具来进行测试,也没有特别去想一些测试点来测试,这次学习了后觉得测试真的作用还是挺大的。还有之前写代码的时候都是直接大概有个思路就开始写,没有像老师作业要求里说的那样按步骤来写程序,这次按着步骤写程序,感觉确实比较有条理性,甚至感觉写出来的代码都比以前更有条理了。
来源:https://www.cnblogs.com/holmec/p/9663687.html