文章目录
这个作业属于哪个课程 | 2022年福大-软件工程 |
---|---|
这个作业要求在哪里 | 软件工程实践第二次作业——个人实战 |
这个作业的目标 | 学习PSP相关知识,单元测试和性能调优 |
其他参考文献 | 无 |
该作业完成对冬奥会的赛事数据的收集,并实现一个能够对国家排名及奖牌个数统计的控制台程序。
Gitcode项目地址
PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 120 | 120 |
• Estimate | • 估计这个任务需要多少时间 | 2400 | 3600 |
Development | • 开发 | 2100 | 3340 |
• Analysis | • 需求分析 (包括学习新技术) | 120 | 160 |
• Design Spec | • 生成设计文档 | 240 | 240 |
• Design Review | • 设计复审 | 60 | 180 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 60 | 40 |
• Design | • 具体设计 | 60 | 180 |
• Coding | • 具体编码 | 1000 | 1840 |
• Code Review | • 代码复审 | 60 | 100 |
• Test | • 测试(自我测试,修改代码,提交修改) | 560 | 600 |
Reporting | 报告 | 180 | 540 |
• Test Repor | • 测试报告 | 120 | 480 |
• Size Measurement | • 计算工作量 | 30 | 30 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 2400 | 4000 |
解题思路描述
如何解析json文件?
- 调用第三方库,但需要选择最适合本项目的解析器,将对各种解析器分别进行单元测试,选择最适合的解析器作为项目默认json解析器,可选解析器有:Gson(最终选择)、FastDFS、Jackson。
- 因为有多种解析器可供选择,所以可以使用设计模型解耦合,减少代码量,也易于单元测试。
如何获取DTO(数据传输对象)?即数据源如何选择?
如果不考虑完全缓存的话,目前有两种解决方案:
- 调用冬奥API
分析:GET请求API,中间涉及到发起网络请求。网络IO可能会是程序的瓶颈段,可以通过多线程异步请求提高吞吐量。因为使用多线程,所以需要线程池管理线程。 - 读取本地数据
分析:根据需求可知,需要的数据已经不会再更新了,因此可以将静态数据文件存放在本地中,在需要时调用操作系统IO读取文件即可。文件IO可能会是程序的瓶颈段,仍然可以通过多线程异步请求IO提高吞吐量,仍然需要线程池。
如何进行优化?
- 根据数据源的两种选择策略可知,程序瓶颈段都会在IO处,所以可以考虑并发读取数据,设置线程池减少线程创建的性能消耗,因此优化时需要考虑核心线程数和选择最优的阻塞队列。
- 考虑到输入文件中可能会有重复的命令,因此可以使用缓存技术减少IO。但是还有一点很重要:如果input文件中重复的命令非常多,程序主要的瓶颈段会变成缓存查找,会转变为cpu密集型任务,核心线程数应该越少越好,减少线程切换次数。
如何实现缓存机制?
-
基于LinkedHashMap实现LRU算法的缓存,效率会稍低于HashMap,但不会发生内存泄漏。
-
使用HashMap,虽然使用HashMap会造成内存泄漏,但本程序非长期运行,只运行一次就结束,因此可以使用。但是会产生线程安全问题,所有需要使用ConcurrentHashMap。
如何解决缓存带来的GC性能问题?
接口设计和实现过程
接口和抽象类的设计
Json解析器接口设计:
为了程序支持不同的Json解析器,使用适配器设计模式,将每个第三方库的反序列化方法统一成deserilize方法进行解耦合,不需要改动代码则可以切换解析器,也易于单元测试。
经过测试,GsonFactory是解析最快的解析工厂。点此查看测试结果
数据源接口设计:
为支持多种数据源,设计出AbstractDataSource抽象类,封装解析命令的方法,而由实例类实现获取奖牌榜json字符串和赛况json字符串的方法。
经过测试,LocalDataSource会比RemoteDataSource提高了1334.6%的运行速度。点此查看测试结果
类图
关键代码展示
初始化启动参数
在main函数启动时,初始化启动参数。会把输入文件和输出文件地址放入应用程序环境中,也会读取启动时设置的json解析器和数据源。
数据源抽象类解析命令逻辑
command由外部传入,可保证前后不含空格。
解析流程如下:
- 判断是否为total命令,不是则判断是否符合schedule命令格式。
- 判断schedule命令格式分为两步:判断格式正确、判断日期正确,这两次判断均使用正则表达式判断。
- 如果schedule命令格式也错误,则返回Error。
- 如果是total命令,则向数据源获取奖牌榜数据,解析后返回。
- 如果schedule命令格式正确,而日期不符合正常日期格式,则直接返回N/A,如果符合正常日期格式,则向数据源获取对应日期数据。
- 如果数据源返回对应日期数据为空,则说明输入日期不在冬奥会期间,没有赛况信息,返回N/A,否则使用数据源的日期数据进行解析后返回。
如果判断命令为total命令,则调用数据源实现类的getTotalMedalsJson方法获取奖牌榜json数据并进行解析。
如果判断命令为schedule命令,则检查格式和简单判断日期格式,如果均符合则调用数据源实现类的getMatchesJson方法获取赛况json数据并进行解析。
Lib类的单例模式实现
处理输入文件
使用try()自动关闭流;
使用线程池的submit方法传入Callable接口返回处理完command的字符串结果。
在线程类中处理缓存,如果命令结果已缓存则直接返回缓存,没有则使用数据源解析命令并缓存。
缓存实现中存在线程安全问题,所以使用ConcurrentHashMap。
对key的处理这里去掉了所有的空格,可以减少重复意义的键值对:
比如 schedule 0215和schedule 0215两种命令就可以用一个key:schedule0215所表示出来。
性能改进
使用多线程提高性能
测试环境:cpu 12核 、JVM采用默认参数、Gson解析器、本地数据源、线程池阻塞队列为100容量的ArrayBlockingQueue
测试数据:
所有命令值各不相同,不会使用到缓存。
单线程执行:
线程池核心线程数为cpu核数两倍:
单线程平均运行时间:210.67ms
多线程平均运行时间:131.00ms
提高了60.1%的运行速度。
选择最优阻塞队列:
ArrayBlockingQueue 队列大小为100
- ArrayBlockingQueue测试结果如下
- LinkedBlockingQueue测试结果如下
- SynchronousQueue测试结果如下
可以看出三者的运行花费速度相差不大,但是考虑到ArrayBlockingQueue和LinkedBlockingQueue会创建队列项,而SynchronousQueue是直接传递任务,在任务数较少的情况下,会稍快一点(已经测试过了),所以最终选择的是SynchronousQueue阻塞队列。
使用本地数据源提高性能
测试环境:cpu 12核 、JVM采用默认参数、Gson解析器、线程池阻塞队列为100容量的ArrayBlockingQueue、核心线程数为cpu核数两倍
远程数据源:
本地数据源:
采用上面的数据。
远程数据源平均运行时间:1879.33ms
本地数据源平均运行时间:131.00ms
提高了1334.6%的运行速度。
使用缓存提高性能
缓存可以减少IO的次数,因此可以提高运行速度。
优化GC
测试环境:jvm启动参数如下
设置堆的大小为20M,可知年轻代的大小为8M。打印GC日志如下:
一共发生3次young gc,没有发生full gc,gc总耗费时间13.3ms。
jvisualvm可视化堆内存如下
可知可以通过提高jvm堆内存大小减少gc次数。
将堆内存设为默认值:
使用jvm默认参数值不会发生GC:
因此针对GC的改进方案是:使用jvm默认堆内存和GC收集器即可。
单元测试
测试各种json解析器解析json的速度:
解析1000条json:
解析10条json:
解析1000000条json:
测试得出gson最适合本项目的json解析,因为本项目采用缓存,最高多解析次数即解析所有不同日期的赛况数据,数量比较小,所有采用gson作为默认json解析器。
测试命令的正则表达式:
测试网络请求工具类:
测试数据源:
还有其他一些小的单元测试可以查看仓库源码。
异常处理
该项目有一个可能会出现的比较严重的异常,即启动应用时输入文件路径异常,该异常不能由程序修复,应抛出异常并让用户知道。
初始化应用环境时会检测输入文件路径是否异常,如果输入文件不存在,则会抛出异常并提示用户输入正确的输入文件路径。如果输出文件路径对应的文件不存在,会自动创建中间目录和文件。启动时输入的路径参数支持相对jar包的路径和绝对路径。
心得体会
通过本次作业:我学习到了
- 从设计角度上:我了解到了PSP表格如何填写,以及第一次实现项目和文档共同构建。
- 从工程角度上:
- 我更理解了各种设计模式对应用扩展性和健壮性起到的重要作用。
- 复习了许多Java基础的API。
- 这也是我第一次将学习到的jvm相关的知识用在了项目中,虽然jvm的默认参数已经是最优的了,但我还是清楚了jvm调优的工作流程,相信在以后的学习工作中会发挥更大的用处。
- 从现实角度上:我更理解了工作中会遇到的与甲方相处的过程:需求的变化和具体化让程序员不停地修改代码与文档。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。