2021年春-OO-第三单元总结
2020春-OO-第三单元总结
一、设计策略分析
在本次作业中,由于是参照JML的代码规格进行编写程序,所以我的代码整体上参照这个规格进行编写,设计策略比较局限,仅在处理图的问题时使用了多种方法。
在处理从JML规格实现代码的过程中,比较重要的是理解JML的特点,针对不同的前置条件,需要有不同的处理方式,并且需要保证我们的处理最终结果和后置条件所定义的一样。
- 从整体上看,有如下几个类:
My***Exception.java
MainClass.java
MyPerson.java
MyNetwork.java
MyMessage.java
|__MyEmojiMessage.java
|__MyNoticeMessage.java
|__MyRedEnvelopeMessage.java
MyGroup.java
-
我们定义了一堆异常类,方便在处理各种异常时进行抛出异常,然后主类控制整个程序的运行,
Person类存放一个人的各种属性(id,姓名,年龄,钱),以及与其他人的关系(是否认识,以及他们的权值),还有收到的信息;MyGroup类存放一组Person,方便对一组人进行管理和共同分发消息等;Message类存放消息,有一个属性为SocialValue,多次进行社交,可以提高个人的社交值,传递的消息既可以从一个人传到另一个人,也可以从一个人传给一组人。它有两个继承的子类,分别是EmojiMessage,RedEnvelopeMessage,区别在于传递的消息的附加效果,RedEnvelope的附加效果是会附加金钱交易,EmojiMessage会增加热度。 -
最后是
NetWork类,它是主要的操作类,内置了大量的操作函数,比如添加Message,发送Message,查询一个组类的平均年龄,添加人,添加关系,查询Person之间的连接关系,查询连接圈等等。 -
我是先实现了异常类,然后再实现Person类、Message类、Group类等较为静态的类,最后才集中实现Network类。这是我的实现流程,算是自底向上的构架程序。
-
从实现JML的规格看,内有许多坑点,JML的优点在于,不关系我们具体使用的数据结构类型,只要保证数据的处理结果正确即可,即前置条件相同,其后置条件得到保证。但是JML的语法,并不能保证时间的优化性,所以需要我们在具体实现的时候,进行适当的优化,比如查询QueryBlockSum时,我采用了二维数组的方式
ArrayList<ArrayList<Integer>> s;,使得互相认识的人(包括经过多个人认识)的id存放在同一个List里,这样在QueryBlockSum时,直接读取s的size(),就能直接得到结果,而不必使用循环递归函数来查询结果。所以在实现时,我并不是完全遵循JML的格式,有些会使用类似的数据结构和处理方法代替原来的JML格式,进而可以节省运行时间。但,对于复杂度较低的地方,我是遵循着JML的规格来的,保证正确性。
二、测试方法和策略
性能测试
我使用了Jprofiler来进行性能测试,它很好地帮我排查了导致CPU运行时间过长的方法。
我的使用它测出的几个点是QueryBlockSum、QueryNameRank运行时间过长,所以我才得到了前文所说的使用二维数组存放相识关系,而不是仅仅使用一维数组,然后每次递归调用islinked来得到结果。QueryNameRank运行时间过长,我直接使用了一个TreeMap<String, Integer> treeMap来存放名字和重名数量,这样Treemap自动按照名字的字典序进行排序,考虑到有重名的,故我右边存放重名的数量,这样在查询的时候,只要进行相加前面的数量即可。
另外一个是我查询到有些小项在同一时间查询数量过多,故我设置了多个Change变量,记录Person和Relation是否改变,每次改变时,我才会重新计算某些值(如平均年龄等)。

JML正确性测试
使用OpenJML,它对JML规格的完整性进行检查,包括许多,如对类型检查,变量的可见性检查,以及可写性检查。
Junit,它可以作为测试框架,编写自己的程序,对代码进行自动化测试,有利于定位bug,但是我自己编写的测试程序,不能逾越自己理解错误JML的鸿沟,所以如果作者本身理解错误JML规格,Junit是无法测试到正确结果的。
三、容器的选择和经验
private ArrayList<ArrayList<Integer>> relation;
private TreeMap<String, Integer> treeMap = new TreeMap<>();
前文已经提到,我是踩过许多坑的,最终选择使用二维数组和Treemap这俩容器存放关键数据,其他普通数据,不涉及复杂度的,都采用了一维ArrayList数组。
前文已经提到,二维数组的使用是为了减小QueryBlockSum的复杂度,仅需要在addRelation时,增加一些操作,比如当不同块的两个人互相认识后,需要合并两个数组,等等,这样每次addrelation,我们就更改relation的构成,保证其每个元素是一个一维数组,相同数组内的人保证可以互相认识或者通过一个或多个人的转接而认识。
最后QueryBlockSum时,直接获取relation数组的元素个数,就得到了块数。
for (int i = 0; i < relation.size(); i++) {
if (relation.get(i).contains(id1)) {
temp1 = relation.get(i);
}
if (relation.get(i).contains(id2)) {
temp2 = relation.get(i);
}
}
if (!temp1.equals(temp2)) {
if (temp1.size() < temp2.size()) {
temp2.addAll(temp1);
relation.remove(temp1);
} else {
temp1.addAll(temp2);
relation.remove(temp2);
}
}
对于Treemap。其目的是保证按照名字是有序的,减少了在QueryNameRank的大量字符串比较,减小了该函数的复杂度,该Treemap,键值对是姓名和数量,因为考虑到有重复名字的,运行时只要每次增加相同名字的个数,最后查询时,直接把该名字之前的元素加起来,就得到了Rank。
int ans = 1;
for (String temp : treeMap.keySet()) {
if (temp.equals(name)) {
break;
}
ans += treeMap.get(temp);
}
return ans;
四、性能分析
-
isCircle容易出现问题,因为后期在测试时,Person的数量剧曾,relation也多,导致在判断iscircle时,需要进行大量的递归调用判断iscircle,(因为两个人不直接相识,需要查看其相识的人是否与目标人物相识)。所以我同样时采用二维数组,把相识的,和通过多个人的转接而相识的一群人放置在同一个一维数组内,这些元素构成了二维数组,在添加relation时,根据情况,合并某些一维数组,使得该二维数组的每个元素内的Person都是
isCircle == true的,最后在判断isCircle时,只需要判断他们是不是在同一个一维数组内即可。for (int i = 0; i < relation.size(); i++) { if (relation.get(i).contains(id1) && relation.get(i).contains(id2)) { return true; } } -
对于sendIndirectMessage,会涉及到计算两个节点(Person)的最短通路问题(Value),我采用了迪杰斯特拉算法,而不是仅仅使用枚举的方式,有效减小了复杂度。
-
在QueryNameRank中,我使用了TreeMap来降低了复杂度,否则对4000多人的姓名字符串比较,会产生大量的无效比较,占用了大量时间。TreeMap在前面已经说到,不再重复。
五、结构设计

架构设计在前文提到:
My***Exception.java
MainClass.java
MyPerson.java
MyNetwork.java
MyMessage.java
|__MyEmojiMessage.java
|__MyNoticeMessage.java
|__MyRedEnvelopeMessage.java
MyGroup.java
我们定义了一堆异常类,方便在处理各种异常时进行抛出异常,然后主类控制整个程序的运行,Person类存放一个人的各种属性(id,姓名,年龄,钱),以及与其他人的关系(是否认识,以及他们的权值),还有收到的信息;MyGroup类存放一组Person,方便对一组人进行管理和共同分发消息等;Message类存放消息,有一个属性为SocialValue,多次进行社交,可以提高个人的社交值,传递的消息既可以从一个人传到另一个人,也可以从一个人传给一组人。它有两个继承的子类,分别是EmojiMessage,RedEnvelopeMessage,区别在于传递的消息的附加效果,RedEnvelope的附加效果是会附加金钱交易,EmojiMessage会增加热度。
最后是NetWork类,它是主要的操作类,内置了大量的操作函数,比如添加Message,发送Message,查询一个组类的平均年龄,添加人,添加关系,查询Person之间的连接关系,查询连接圈等等。

浙公网安备 33010602011771号