oo第三单元总结
oo第三单元总结
实现规格所采取的设计策略
只梳理一下几个与规格的实现方法差异较大的接口,像Message这种直接照着规格写就行了
接口 | 数据结构实现 | 方法实现 |
---|---|---|
Person | HashMap<Integer, Integer> acquaintance保存熟人,key代表熟人的id,value代表到熟人的权值 | 直接用get实现查找熟人;返回acquitance在Network中插入熟人 |
LinkedList<Message>messages保存接收到的消息,新消息采用头插的方式,比数组更快 | 直接通过前几个消息的subList新建LinkedList并返回 | |
int root用来保存并差集的根结点 | 通过getRoot和setRoot直接返回root,在Network类中处理并差集 | |
Group | HashSet<Person>people保存组内的所有人 | getAgeVar方法由于有取整操作的存在,不好实时更新,故采用静态写法,每次遍历people进行计算 |
int valueSum保存组内所有人的socialValue总和,每次加人减人都实时更新 | ||
BigInteger ageSum保存组内所有人的年龄和,用来实时更新平均年龄 | ||
Network | HashMap<Integer, Person>people保存所有人 | getRoot(Myperson)以递归的方式获取person的根,setRoot方法也类似 |
ArrayList<String>names保存所有人的名字,为有序数组,用来二分快速查找名字排序 | queryReachableAndSetRoot方法为dfs查询p1能不能到p2,如果能的话就把路径上的所有点设置为同一个root;这个函数只在addRelation时调用,用来实时更新联通块的数量 | |
HashMap<Integer, Group>groups用来快速查找组 | addRelation主要完成两件事,一是设置两人的关系并调整并差集和联通块数目,二是更新所有包含此两人的组的valueSum | |
HashMap<Integer, Message>messages用来快速查找消息 | queryNameRank通过二分查找名字的最左端下标来返回相应排名 | |
HashMap<Integer, Integer>emojis用来保存emoji表情,key为emoji表情的id,value为emoji表情的热度 | addPerson主要完成三件事,一是加入person到容器中,二是二分插入name,三是增加联通块数目 | |
TreeMap<Integer, Set<Integer>>emojiHeat用来表示emoji表情的热度,key为emoji表情的热度,value为热度为key的emoji表情的集合 | addMessage除了正常加入message到容器中,还要特判emojiMessage,并加入相应的emojiMessages中 | |
HashMap<Integer, Set<Integer>>emojiMessages表示相应的emojiMessage,key为emoji表情的id,value为emoji为key的消息的集合 | sendIndirectMessage中主要是调用了getMinPath方法,通过用最小堆实现的Dijistra算法静态地计算出最短路 | |
int rootCount用来实时记录联通块的数量 | delColdEmoji方法主要通过emojiHeat这个有序树结构来删除前几个热度的所有emoji和相应的emojiMessage,避免了遍历 |
基于JML规格设计测试
JML规格比较适合于设计单元测试,考虑用Junit工具实现:
- requires: 测试不同情况下的require,保证数据覆盖完全
- ensures: 每个ensure直接照着规格写即可
- exceptions: 异常也要进行相应检测
- invariant: 不变式可在每调用一次方法都进行检查
至于正常数据类型下的测试,为了保证数据的有效性,也可以通过中间小,两端大的概率分布生成极端情况多,普通数据少的测试数据
还要注意的是自己新加的类也最好写规格进行相应的单元测试(比如最小堆的插入算法一开始就写错了)
容器选择和使用
最优化目标为减少遍历循环的次数:
- 只用无序查找和插入:HashMap
- 只用头插入和头截取:LinkedList
- 需要有序查找次序:有序ArrayList
- 需要有序查找和删除:TreeMap
性能问题
问题 | 解决办法 |
---|---|
最短路查询过慢 | 最小堆实现的Dijistra贪心算法 |
用最小堆实现的Dijistra中需要用HashSet判断结点是否访问过,可以构造大量哈希冲突的数据拖慢查询效率 | 用TreeSet有序容器 |
联通块数目查询过慢 | 并差集 |
若为有向图,则加边时需要查询两点是否相互联通,queryValueAndSet中dfs层数会很深,导致效率低下甚至爆栈 | 手动写栈;采用惰性实时更新,只在查询联通时一并更新并差集,并标记为最新 |
delColdEmoji方法需要遍历所有emoji | 用TreeMap以热度作为key从小到大进行遍历 |
每次addRelation都要遍历所有组 | 采用惰性实时更新,只在查询相应组的socialValue时实时更新,并标记为最新 |
架构设计
类图如下
图的构建采用实时构建的方法,每加一个点、边就改变一次,通过MyPerson类中的HashMap实现边