第4周小组作业:WordCount优化
Posted 母翟龙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第4周小组作业:WordCount优化相关的知识,希望对你有一定的参考价值。
1.github地址
https://github.com/muzhailong/wcPro
2.填写PSP表格
PSP阶段 | 预计耗时(分钟) | 实际耗时(分钟) |
计划 | 10h | 15h |
.估计这个任务需要时间 | 10h | 15h |
开发 | 9h | 13h |
.需求分析(包括学习新技术) | 5 | 20 |
.生成设计文档 | 15 | 30 |
.设计复审 | 10 | 15 |
.代码规范 | 10 | 10 |
.具体设计 | 7h | 10 |
.具体编码 | 10 | 10h |
.代码复审 | 30 | 15 |
.测试 | 40 | 80 |
报告 | 1h | 2h |
.测试报告 | 25 | 1.5h |
.计算工作量 | 15 | 10 |
.事后总结并提出改进计划 | 20 | 20 |
合计 | 10h | 15h |
3.描述代码设计思路
我们小组讲这个工程分成类7大模块:
param:参数解析模块
in :输入模块
core :核心处理模块
out :输出模块
ui :图形界面模块
util :工具类模块
start: 模块集成模块
我主要负责的是输入模块、核心模块以及模块集成模块,各个模块如下图所示:
in(输入模块):
功能:负责从指定文件中读取内容,并且为核心模块提供单词(next方法)。
实现:思路很简单,现将指定文件的所有内容读取到内存中去(preRead方法实现,比较简单不多说,这个地方可以有优化,后面再说),然后通过next方法解析单词,重点说一下next方法的实现,首先我是基于这样一个事实单词只能又字母和横线构成,因此我只需要三次循环即可获取一个单词,第一次循环找到第一个字母的位置p,第二次循环从p开始找到连续的字母和横线,第三次循环,去掉末尾的横线。如果读出来的单词长度为0需要继续再次读取。
简单贴一下代码:
1 public String next() { 2 StringBuilder word = new StringBuilder(); 3 int len = sb.length(); 4 if (pos >= len) 5 return null; 6 int t = -1; 7 char c = 0; 8 while (t < 0 && pos < len) { 9 while (pos < len && isNomalChars(sb.charAt(pos))) { 10 ++pos; 11 } 12 while (pos < len && isLetterChars(c = sb.charAt(pos))) { 13 word.append(c); 14 ++pos; 15 } 16 t = word.length() - 1; 17 while (t >= 0 && isShortLine(word.charAt(t))) { 18 --t; 19 } 20 } 21 String result = word.substring(0, t + 1); 22 return result.length() == 0 ? null : result; 23 }
core(核心模块):
功能:实现单词的统计计数,以及获取前100个单词。
实现:我使用的是散列表,可以有较快的查找速度,这个模块通过run方法使用in模块的接口将单词加入到散列表中,这个模块的核心是如何快速查找前100的单词,即(sort如何实现),这个地方怎么说呢?我最开始用的是优先队列,本来以为效率不怎么好(一般来说堆排序效率确实不如快排)但是当我最后进行性能优化的时候竟然出奇的发现使用优先队列比快排、甚至是只循环100次+散列表查找都快。这就导致我没有优化的余地了,为了迎合老师的任务,我这里假装使用的不是优先队列,而是一个最垃圾的冒泡排序(反正我感觉在排序中用冒泡真的很zz,冒泡排序没一点好处,速度比不少快排,堆排序,甚至插入排序都可以吊打他,哎!假装不是我用的排序方法),排完序之后就很简单了,之久拿出前100个(这里需要考虑一下元素有没有100个)返回就ok了。
简单贴一下冒泡排序的代码,纯的冒泡排序就不解释了(假装不是写的):
1 protected void defaultSort() { 2 int sz=mp.size(); 3 List<Entry<String,Integer>>lt=new ArrayList<Entry<String,Integer>>(sz); 4 for(Entry<String,Integer>e:mp.entrySet()) { 5 lt.add(e); 6 } 7 for(int i=0;i<sz-1;++i) { 8 for(int j=0;j<sz-i-1;++j) { 9 if(cmp.compare(lt.get(j), lt.get(j+1))>0) { 10 Entry<String,Integer>tmp=lt.get(i); 11 lt.set(i,lt.get(i+1)); 12 lt.set(i+1, tmp); 13 } 14 } 15 } 16 17 for(int i=0;i<SZ&&i<sz;++i) { 18 res.add(lt.get(i)); 19 } 20 }
start(模块集成模块):
功能:因为整个工程是面向模块的,最终需要将所有模块进行集成来构建整个项目。
实现:实现很简单,主要考虑考虑三种情形,1.命令正确并且包含了指定文件名。2.命令正确为“-x”(我们在刚开始的时候就考虑附加题了)。3.命令错误。
针对这三种命令,将参数传递给param模块进行参数解析,然后得到Options类,这个类中包含了所有的启动信息。然后根据Options启动相应的模块即可。
简单贴一下Options(属于Param模块的静态内部类)的代码:
1 public static class Options{ 2 public boolean isX;//图形界面 3 public boolean isConsole;//控制台 4 public boolean isErr;//错误 5 private String fn;//文件名 6 private String info;//启动信息 7 public void setFn(String fn) { 8 this.fn=fn; 9 } 10 public String getFn() { 11 return fn; 12 } 13 14 public String getInfo() { 15 return info; 16 } 17 18 public void setInfo(String info) { 19 this.info=info; 20 } 21 }
4.测试设计过程
测试用例的设计主要分模块内测试和模块间测试(集成测试)可以点击查看。
因为我主要做得是start、in、core模块测试设计很简单,
对start模块主要采用黑盒测试/边界测试,因为start是将所有模块集成起来,不易进行白盒测试。
in模块,主要进行白盒测试,其中使用了语句覆盖,分支覆盖,条件覆盖等。因为in模块涉及到很多字符的判断,内部逻辑需要进行比较完整的测试。
core模块,采用黑盒和白盒测试方法,对其中的逻辑进行黑盒测试,然后再将所有功能合并进行黑盒测试。
满足效率要求:使用参数化、套包的方式可以极大的提升效率。
5. 测试运行与评价
单元测试截图:
start:
in:
core:
评价单元测试用例效果:
感觉我做的单元测试用例挺好的大部分都符合单元测试的集合要素(貌似是:自动化,灵活,数据集合,报告记录)。start模块主要采用的是黑盒测试,因为start模块集成了所有的模块,白盒测试部分只是做了一部分的判断逻辑等。in模块应该来说是测试最为复杂的模块,内部方法比较多,针对每一个方法进行测试然后使用套包的方法测试,效率还是挺不错的。core模块相应的逻辑比较少,因为core模块的很多东西都依赖与in模块,所以就对接口的相关地方进行的白盒测试。
评价被评测模块的质量水平:
start模块:中
in模块:高
core模块:高
6.开发规范说明:
开发规范采用的是《阿里巴巴Java开发手册终极版v1.3.0.pdf》
选定的开发规范以及理解(我用以下的规范检查我的代码):
常量定义
1. 【强制】不允许任何魔法值(即未经定义的常量)直接出现在代码中。
理解:就是说常量不能使用变量拼接而成的。
反例:String key = "Id#taobao_" + tradeId; cache.put(key, value);
2. 【强制】long 或者 Long 初始赋值时,使用大写的 L,不能是小写的 l,小写容易跟数字 1 混 淆,造成误解。
理解:很简单不解释
举例:long id=100L;
3. 【推荐】不要使用一个常量类维护所有常量,按常量功能进行归类,分开维护。 说明:大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。
理解:有时候我们会专门写一个类来维持常量,这样是不好的因为将所有的常量放在一起就像一个大杂烩一样。应该根据功能进行放置,比如说和单词容量有关的常量可以放在单词的工厂类中。
代码格式
1. 【强制】大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果 是非空代码块则:
1) 左大括号前不换行。
2) 左大括号后换行。
3) 右大括号前换行。
4) 右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行。
理解:就是字面意思,不用解释。
举例:if(true){
......
}
2. 【强制】 左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格。
比如:void pre();
3. 【强制】if/for/while/switch/do 等保留字与括号之间都必须加空格。
说明:格式上看的清晰一点。
举例:while (t<0) {
.......
}
4. 【强制】任何二目、三目运算符的左右两边都需要加一个空格。
举例:c = a + b;
5. 【强制】采用 4 个空格缩进,禁止使用 tab 字符。
说明:很多IDE都可以讲tab设置为4个空格。
6. 【强制】注释的双斜线与注释内容之间有且仅有一个空格。
举例:// ok
7. 【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。
举例:void f(int a, int b)
OOP 规约
1. 【强制】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成 本,直接用类名来访问即可。
理解:静态方法属于类,和类是绑定的,通过对象还要先找到类,在从类找到方法。
举例:
public class Test{
public static int id;
}
使用Test.id即可。
2. 【强制】所有的覆写方法,必须加@Override 注解。
理解:Override注解貌似是编译时检查,如果重载出现问题编译时不会通过的。
举例:
public class Father{
public void say(){}
}
public class Child extends Father{
@Override
public void say(){}
}
3. 【强制】不能使用过时的类或方法。
理解:一般来说过时的方法都会有风险,或者效率问题。
举例:acm中最喜欢用的StreamTokenizer
4. 【强制】构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。
理解:讲逻辑放在构造方法中会造成构造对象浪费大量的时间,有些对象构造了但不一定会使用。
举例:比较简单不举例了。
5. 【推荐】循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。
理解:+相当于使用多个StringBuilder对象,效率很低下。
举例:String s="123";
s=s+"sajfd"+"dd";
正例:StringBuilder sb=new StringBuilder();
sb.append("abc");
sb.append("chaj");
6. 【推荐】类成员与方法访问控制从严:
1) 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。
2) 工具类不允许有 public 或 default 构造方法。
3) 类非 static 成员变量并且与子类共享,必须是 protected。
4) 类非 static 成员变量并且仅在本类使用,必须是 private。
5) 类 static 成员变量如果仅在本类使用,必须是 private。
6) 若是 static 成员变量,必须考虑是否为 final。
7) 类成员方法只供类内部调用,必须是 private。
8) 类成员方法只对继承类公开,那么限制为 protected。
理解:这个符合最小访问原则,不想吐槽了,我最开始的时候确实都是符合的,但是为了测试,哎我已经不是我了,全是public
集合处理
1. 【推荐】集合初始化时,指定集合初始值大小。
理解:看过源码就知道,ArrayList默认大小是10 Map的默认大小是16,他们一般都会在容量达到75%的时候进行扩容处理,所以如果初始时候知道了容量可以避免扩容的开销,提升效率。
举例:
List<String>lt = new ArrayList<String>(100);
2. 【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。
理解:看过Map源码就知道,HashMap在内部维持了一个Entry的数组,使用entrySet遍历其实就是遍历数组,如果使用keySet就行遍历,是讲所有的key打包成一个set然后再通过遍历key从map中获取相应的value,效率低下。
举例:
for(Map.Entry<String,Integer>e:mp.entrySet()){
.....
}
7.交叉代码评审
评审代码的同学17063:(吐槽一下要是我检查的同学代码没有问题我是不是应该让他改的有问题呀?哎!)
评审的模块out:
存在的问题:
1.为了迎合测试代码的访问权限设置非常不合理
2.拼接字符串使用的是+(我觉得这个可以理解因为他就只是拼接了3个字符串不是很多的情况下我觉得使用+可以的)
遵循好的规范:
上面规范中的代码规范他都遵守了(哎!其实这个是IDE的功劳,现代IDE都有代码格式化的功能)。
8.静态代码扫描
使用的是阿里巴巴的p3c 我使用的是eclipse在线安装地址 https://p3c.alibaba.com/plugin/eclipse/update
警告:5
错误:0
运行截图:
问题分析:抽象类应该使用AbstractCounter命名。
问题分析:java.util.PriorityQueue 未使用。
问题:Collection是原生类型,应该使用泛型。
问题分析:capacity未使用。
问题分析:Collection是原生类型。
9.组内代码分析
我们小组的代码整体上质量比较高。原因如下:
1.整个项目划分成7大模块,模块之间耦合性很低,各个模块之间可以并行开发,开发效率比较高。
2.项目代码采用阿里巴巴的java开发规范,各个方面方面遵循一定的规范。
3.真个小组使用p3c进行静态检查,0警告,0错误。
4.小组内人员齐心合力,认认真真,仔仔细细修改每一个地方。
5.小组内遇到逻辑上的问题互相讨论,达到最优方案。
10.测试数据集
设计思路:非常简单,我们在开发的时候已经考虑到测试了,因此预留下了一个util模块,其中util模块中有一个方法可以生成一个文件。
生成随机文件的思路很简单,通过随机数然后将指定字符写入文件即可。后面的测试只需要执行randomFile方法就可以生成一个指定大小的文件测试起来很方便。
1 package com.util; 2 3 import java.io.BufferedWriter; 4 import java.io.File; 5 import java.io.FileWriter; 6 import java.io.IOException; 7 import java.util.Random; 8 9 public class Utils { 10 11 private Utils() { 12 } 13 14 private static Random random = new Random(); 15 private static String str = "abcddefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~`!" 16 + "#%^&*_…()[]+=-:\'\\"|<>,./? \\n\\t\\r0123456789"; 17 18 public static void randomFile(String fn, int sz) {//sz 单位字节 19 File f = new File(fn); 20 StringBuilder sb = new StringBuilder(sz * 2 / 3); 21 BufferedWriter writer = null; 22 try { 23 writer = new BufferedWriter(new FileWriter(f)); 24 char c = 0; 25 int len = str.length(); 26 for (int i = 0; i < sz; ++i) { 27 c = str.charAt(random.nextInt(len)); 28 sb.append(c); 29 } 30 writer.write(sb.toString()); 31 } catch (IOException e) { 32 e.printStackTrace(); 33 } finally { 34 try { 35 writer.close(); 36 } catch (IOException e) { 37 e.printStackTrace(); 38 } 39 } 40 } 41 public static void main(String[] args) { 42 String fn="1.txt"; 43 int sz=1024*1024*40;//40M 44 randomFile(fn,sz); 45 } 46 }
11.同行评审过程描述
角色划分:
作者、讲解员:我(17056)
记录:王
评审:吴
主持人:邓
目的:针对可能影响或制约程序性能指标的主要因素加以讨论
分析:程序的主要开销集中在文件操作上,提取单词的算法,排序的算法等。
结论:
影响程序性能的主要因素有:
1.读取文件的开销。
2.从文件中获取单词的算法
3.单词的保存以及查找算法
4.排序算法(如果使用排序的话)
5.写入文件的开销
6.文件本身的内容(随机因素不考虑)
12.性能分析
实际测试的结果是我们在文件的处理方面已经做得足够好了,对性能造成影响的地方发现是缓冲区的大小设置,还有排序算法的影响。其中排序算法造成很大影响。
13.性能优化
设计思路:打算先从缓冲区的大小设置开始找到最优值,然后改进排序算法,使用更好的快排或者堆排序。
优化之后基本上3-4s可以跑完40M的文本。
14.作业小结
这次作业最让我郁闷的是
1.我们为了让性能改进的地方有更多可写的内容,把我们最初优秀的方案,改成不忍直视的方案,其中不得不说的就是冒泡排序,哎!这种垃圾排序算法不知道现在还有地方用吗?
2.我们为了更好的测试修改了产品代码,我真的不能忍受的很呢。即使使用反射我也觉得ok。感觉这次作业有点测试过度了。
3.不得不吐槽的是,扩展和附加题完全与分数不成正比呀。这次作业感觉测试代码都比产品代码多,终于体会到了测试占整个项目的80%。
4.这次作业最坑的是给的常规字符里面有个中文的半省略号,哎!英文里面套中文坑呀。
4.个人觉得高级功能中的优化应该改一下,像这个小的项目,有一些大佬一开始就能把所有可以优化的地方全都注意到了,但是为了迎合这个高级功能,不得不写一些比较水的 程序,我觉得这真心有点违背测试的原意呀。
5.我好菜呀,需要努力!
15.图形用户界面
这次附加题真心简单呀,5分钟就可以搞定。实现思路很简单,我们这个项目有7大模块,其中一个就是param参数解析模块,当遇到“-x” 就执行ui模块获取到文件之后就和控制台应用程序没区别了。
16.小组贡献率
17056:0.46
17051:0.18
17042:0.17
17063:0.19
以上是关于第4周小组作业:WordCount优化的主要内容,如果未能解决你的问题,请参考以下文章