个人作业2——WordCount
个人作业2——WordCount
标签(空格分隔): 软件工程
Task1:Fork仓库的码云地址
码云地址:https://gitee.com/JiuSong/PersonalProject-Java
Task2:PSP表格
PSP2.1 | 个人开发流程 | 预估耗费时间(分钟) | 实际耗费时间(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 20 |
· Estimate | 明确需求和其他相关因素,估计每个阶段的时间成本 | 20 | 20 |
Development | 开发 | 240 | 300 |
· Analysis | 需求分析 (包括学习新技术) | 20 | 30 |
· Design Spec | 生成设计文档 | 20 | 25 |
· Design Review | 设计复审 | 20 | 20 |
· Coding Standard | 代码规范 | 10 | 10 |
· Design | 具体设计 | 20 | 26 |
· Coding | 具体编码 | 120 | 150 |
· Code Review | 代码复审 | 20 | 25 |
· Test | 测试(自我测试,修改代码,提交修改) | 60 | 70 |
Reporting | 报告 | 80 | 110 |
· | 测试报告 | 20 | 25 |
· | 计算工作量 | 10 | 15 |
· | 并提出过程改进计划 | 20 | 15 |
Task3:解题思路描述
题目的需求分析:
- 统计文件的字符数:
- 只需要统计Ascii码,汉字不需考虑
- 空格,水平制表符,换行符,均算字符
- 统计文件的单词总数,单词:至少以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写。
- 英文字母: A-Z,a-z
- 字母数字符号:A-Z, a-z,0-9
- 分割符:空格,非字母数字符号
- 例:file123是一个单词, 123file不是一个单词。file,File和FILE是同一个单词
- 统计文件的有效行数:任何包含非空白字符的行,都需要统计。
- 统计文件中各单词的出现次数,最终只输出频率最高的10个。频率相同的单词,优先输出字典序靠前的单词。
- 按照字典序输出到文件result.txt:例如,windows95,windows98和windows2000同时出现时,则先输出windows2000
- 输出的单词统一为小写格式
- 输出的格式为
characters: number
words: number
lines: number
<word1>: number
<word2>: number
...
思路分析:
刚拿到题目时,就先分析了一下需求,经过网上查找资料,经过如下分析过程:
- 问题首先需要解决的是如何读文件?如何写文件?整个的类的封装要怎么构造。
- 统计文件字符数时,又有一个重点就是不区分大小写并且是输出单词统一为小写格式,所以我想到的是先对文件进行处理,读出来的整个内容赋予给一个字符串,在进行单词数量统计之前将字符串统一转化为小写格式
string.toLowerCase()
- 题目要求讲的单词至少4个英文字母开头,所以
FILE1234
可以作为单词,但123FILE
这就不能算作单词。又因为要统计单词的频率,所以可以用Map来写,单词作为Key,频数作为Value值。 - 统计总的字符数,可以对全部内容进行读取,用
换行('\n')、回车('\r')
来进行数组的切分,这样每一组都是原来的一行,然后对每一个数组元素去除空格后进行比较,如果空则原来是空内容的一行。
最后综合以上分析可以得到流程图如下:
Task4:设计实现过程
一、相关类的设计
针对这个要求,得到一个主函数,两个类,其中:
- Word类:进行字符数量、行数、单词数、词频的统计。
- OperateFile类:进行文件内容的读取,以及处理结果的写入。
- Main类:结合实际情况,创建以上两种对象,进行方法的调用,实现题目要求。
二、相关函数的设计
Word:
- wd.getcharNum(); //字符数统计
- wd.getwordNum();//单词数统计
- wd.getlineNum();//有效行统计
- wd.getwordFreq();//单词频率统计
OperateFile:
- fd.FileToString(file);//读出文件内容变成字符串
- fd.WriteToFile(w);//结果写入指定文件
Task5:代码说明
1. getcharNum()
//统计文件的字符数:只需要统计Ascii码,汉字不需考虑空格,水平制表符,换行符,均算字符
public int getcharNum()
{
char ch;
for(int i = 0;i<str.length();i++)
{
ch = str.charAt(i);//String str = "abc";char ch = str.charAt(0);char ch2 = str.charAt(1);这时候ch是a,ch2是b;
if(ch>=32 && ch<=126 || ch == '\n' || ch == '\r' || ch == '\t') //换行('\n')、回车('\r')、水平制表符('\t')、垂直制表符('\v')
{
charNum++;
}
}
return charNum;
}
2. getwordNum()
//统计文件的单词总数,单词:以4个英文字母开头,跟上字母数字符号,单词以分隔符分割,不区分大小写。
节选:
for(int i = 0;i<everyword.length;i++)
{
if(everyword[i].length()<4)
{
continue;
}
else //符合长度的单词
{
int flag=0;
char[] ch = everyword[i].toCharArray();//每一个everyword数组内容拆分成字符数组
for(int j = 0;j<4;j++)
{
if(!(ch[j]>= 'A' && ch[j]<= 'Z' || ch[j]>= 'a' && ch[j]<= 'z'))
{
flag=1;
}
}
if(flag ==0 )
{
wordNum++;
}
}
}
3. getlineNum()
//统计文件的有效行数:任何包含非空白字符的行,都需要统计。
public int getlineNum()
{
String[] line = str.split("\n|\r");//换行('\n')、回车('\r')
for(int i=0; i<line.length;i++)
{
if(line[i].trim().isEmpty())
{
continue;
}
else
{
lineNum++;
}
}
return lineNum;
}
4. getwordFreq()
//单词频率统计
public List<Map.Entry<String, Integer>> getwordFreq()
{
wordFreq = new HashMap<String,Integer>();
String s = str;
s = s.replace('\n',' ');
s = s.replace('\r',' ');
s = s.replace('\t',' ');
String[] everyword = s.split(" ");//用空格作为分割
for(int i = 0;i<everyword.length;i++)
{
if(everyword[i].length()<4)
{
continue;
}
else
{
int flag=0;
char[] ch = everyword[i].toCharArray();
for(int j = 0;j<4;j++)
{
if(!(ch[j]>= 'A' && ch[j]<= 'Z' || ch[j]>= 'a' && ch[j]<= 'z'))//开头四位不全为字母
{
flag=1;
}
}
if(flag ==0 )//符合一个单词的标准
{
String key = everyword[i].trim().toLowerCase();//统一转换成小写
if (wordFreq.containsKey(key)){
int n=Integer.parseInt(wordFreq.get(key).toString())+1;
wordFreq.put(key,n);
}else {
wordFreq.put(key,1);
}
}
}
}
List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(wordFreq.entrySet());
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
@Override//降序排序
public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
// TODO Auto-generated method stub
if(o1.getValue() == o2.getValue())
{
return o1.getKey().compareTo(o2.getKey());
}
return o2.getValue().compareTo(o1.getValue());
}
});
return list;
/* for (Entry<String, Integer> mapping : list) { //输出
System.out.println(mapping.getKey() + ":" + mapping.getValue());
} */
}
该函数用于单词词频的统计,按照要求首先判断词是否为单词,然后实验map进行储存,由于map的key不可重复,则每次写入时需要判断map之前是否存在过该数据,若没有存在过,则该value值为1,否则,在其value值上再加1.
统计后,将map改为list存储,再用Collections.sort进行排序,通过重写comparator来实现要求的排序。
Task6:单元测试
根据以上函数,设置了一些测试点,尽可能罗列出各种情况:
- 字符数为空
- 完全的中文文件
- 完全的英文文件
- 完全的英文文件,大小写混合
- 完全的英文文件,无合法单词
对应设置的文件为:
- test1:完全的空文件
- test2:纯中文文件,不含有英文
- test3:纯英文文件,完全小写
- test4:纯英文文件,大小写混合
- test5:英文、数字、特殊符号混合
测试代码:
package WordCount;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import org.junit.Before;
import org.junit.Test;
public class WordTest {
//String file1 = "text1.txt";
//String file2 = "text2.txt";
//String file3 = "text3.txt";
//String file4 = "text4.txt";
/* Scanner sc =new Scanner(System.in);
String file1 = sc.next();
String file2 = sc.next();
String file3 = sc.next();
String file4 = sc.next();
*/
@Before
public void setUp() throws Exception {
}
//统计字符数量测试
@Test
public void testGetcharNum() throws IOException {
OperateFile fd = new OperateFile();
// System.out.print(file1);
String str1 = fd.FileToString("test1.txt");//读出文件内容变成字符串
String str2 = fd.FileToString("test2.txt");
String str3 = fd.FileToString("test3.txt");
String str4 = fd.FileToString("test4.txt");
Word wd1 = new Word(str1);
Word wd2 = new Word(str2);
Word wd3 = new Word(str3);
Word wd4 = new Word(str4);
int p1 = wd1.getcharNum();//调用
int p2 = wd2.getcharNum();
int p3 = wd3.getcharNum();
int p4 = wd4.getcharNum();
assertEquals(0, p1);//测试与结果匹配
assertEquals(37, p2);
assertEquals(86, p3);
assertEquals(272, p4);
}
@Test
public void testGetwordNum() throws IOException {//统计单词数量测试
OperateFile fd = new OperateFile();
String str1 = fd.FileToString("test1.txt");//读出文件内容变成字符串
String str2 = fd.FileToString("test2.txt");
String str3 = fd.FileToString("test3.txt");
String str4 = fd.FileToString("test4.txt");
Word wd1 = new Word(str1);
Word wd2 = new Word(str2);
Word wd3 = new Word(str3);
Word wd4 = new Word(str4);
int p1 = wd1.getwordNum();//调用
int p2 = wd2.getwordNum();
int p3 = wd3.getwordNum();
int p4 = wd4.getwordNum();
assertEquals(0, p1);//测试与结果匹配
assertEquals(0, p2);
assertEquals(10, p3);
assertEquals(35, p4);
}
@Test
public void testGetlineNum() throws IOException {//统计有效行数测试
OperateFile fd = new OperateFile();
String str1 = fd.FileToString("test1.txt");//读出文件内容变成字符串
String str2 = fd.FileToString("test2.txt");
String str3 = fd.FileToString("test3.txt");
String str4 = fd.FileToString("test4.txt");
Word wd1 = new Word(str1);
Word wd2 = new Word(str2);
Word wd3 = new Word(str3);
Word wd4 = new Word(str4);
int p1 = wd1.getlineNum();//调用
int p2 = wd2.getlineNum();
int p3 = wd3.getlineNum();
int p4 = wd4.getlineNum();
assertEquals(0, p1);//测试与结果匹配
assertEquals(6, p2);
assertEquals(4, p3);
assertEquals(12, p4);
}
@Test
public void testGetwordFreq() throws IOException {//统计词频测试
OperateFile fd = new OperateFile();
String str1 = fd.FileToString("test1.txt");//读出文件内容变成字符串
String str2 = fd.FileToString("test2.txt");
String str5 = fd.FileToString("test5.txt");
Word wd1 = new Word(str1);
Word wd2 = new Word(str2);
Word wd5 = new Word(str5);
List<Map.Entry<String, Integer>> wordFreq1 = wd1.getwordFreq();
List<Map.Entry<String, Integer>> wordFreq2 = wd2.getwordFreq();
List<Map.Entry<String, Integer>> wordFreq5 = wd5.getwordFreq();
Map<String,Integer> w1 = new HashMap<String,Integer>();
Map<String,Integer> w2 = new HashMap<String,Integer>();
Map<String,Integer> w3 = new HashMap<String,Integer>();
// Map<String,Integer> w4 = new HashMap<String,Integer>();
w3.put("jiusong", 4);
w3.put("test111", 1);
List<Map.Entry<String, Integer>> t1 = new ArrayList<Map.Entry<String, Integer>>(w1.entrySet());
List<Map.Entry<String, Integer>> t2 = new ArrayList<Map.Entry<String, Integer>>(w2.entrySet());
List<Map.Entry<String, Integer>> t3 = new ArrayList<Map.Entry<String, Integer>>(w3.entrySet());
assertEquals(t1, wordFreq1);
assertEquals(t2, wordFreq2);
assertEquals(t3, wordFreq5);
/*
jiusong=4
test111=1*/
}
}
测试结果:
分值覆盖率截图:
代码内的覆盖率:
在右侧工具栏中点击”Coverage”图标,打开Coverage的视图后,代码字体背景变红/变绿/变黄原因,经过百度后,有关覆盖测试的颜色含义如下:
Source lines containing executable code get the following color code:
1.green for fully covered lines,
2.yellow for partly covered lines (some instructions or branches missed) and
3.red for lines that have not been executed at all.
即:
- 绿色为完全覆盖的线条,
- 黄色部分覆盖的线条(一些指示或分支遗漏)
- 红色表示尚未执行的行
测试的代码中,在word处理类里面这两部分吗没有完全被覆盖:
Task7:效能分析
分析图,由Jprofiler生成:
运行结果:
分析:
根据以上生成的图,可知道char[]、String用得比较多。在统计文件的单词总数中,有用到char[],嵌套在一个大的for循环中,可能是因为循环的缘故导致调用的频率比较高。因为对一个文件运行时,相当于把整个文件中的每个字符都变成了char[]中的一员,数量比较庞大。
Task8:心得体会
在这次试验中,我发现自己学到了很多编写代码以外其他的很多内容,一个项目的核心不仅仅是代码,它的性能以及准确性也十分重要。之前我以为做软件就是要编写代码,现在我发现做单元测试进行分析是一件十分有意思的事情,尤其是eclemma得到的覆盖率,每个调用的多少频率如何,这可以帮助我们找到自己编写的代码的思维逻辑上的错误,更加快速准确的找到问题所在。
后面的jprofiler也十分的厉害,但是安装消耗了很久的时间,安装出了问题的解决也用了很久,可能是网上关于出错的教程太少,很多问题很难解决。还有就是这个软件只能做到很表面的一个小测试,有些测试给出的结果图有些看不懂,分析起来也很费劲。所以这边的截图只能粗略浅显的大概进行了一波分析。
总的来讲,这一次学到了很多,让我的观点也发生了一些改变,我觉得软件不再是单纯地写代码枯燥的事情,还可以进行分析进行测试,加大了我对这个事情的一个兴趣度。