软件工程第二次作业--文件读取

本文介绍了如何通过FastJson解析JSON数据、从网站抓取比赛数据、设计接口实现对世界游泳锦标赛跳水项目数据的统计,以及后续的性能改进、单元测试和异常处理。作者通过实际操作展示了软件工程实践中的关键步骤和经验体会。
摘要由CSDN通过智能技术生成
这个作业属于哪个课程2302软件工程社区
这个作业要求在哪里软件工程第二次作业–文件读取
这个作业的目标实现一个对世界游泳锦标赛跳水项目相关赛事数据进行统计的控制台程序
其他参考文献《构建之法》、CSDN、工程师的能力评估和发展单元测试和回归测试

1. Gitcode项目地址

仓库地址

2. PSP表格
PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划6040
• Estimate• 估计这个任务需要多少时间2015
Development开发9901375
• Analysis• 需求分析 (包括学习新技术)100200
• Design Spec• 生成设计文档3025
• Design Review• 设计复审2020
• Coding Standard• 代码规范 (为目前的开发制定合适的规范)3040
• Design• 具体设计6050
• Coding• 具体编码600660
• Code Review• 代码复审3020
• Test• 测试(自我测试,修改代码,提交修改)120360
Reporting报告6090
• Test Repor• 测试报告2030
• Size Measurement• 计算工作量1020
• Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划3040
合计11101505
3. 解题思路描述
3.1 数据的获取

获取运动员的数据相对简单,此处以获取比赛结果的数据作为示例。

  1. 打开所要获取数据的网站(Competition Athletes | World Aquatics Official),找到界面下方导航栏下的RESULTS,点击进入后按F12打开开发者工具img
  2. 发现events.json的请求接口地址为https://api.worldaquatics.com/fina/competitions/2969/events
    在这里插入图片描述
  3. 分析events.json的结构,得到DisciplineList中的Id为比赛项目的Id,DisciplineName为比赛项目的名称
    在这里插入图片描述
  4. 在页面上点击切换比赛项目,注意到页面请求了对应比赛的json文件img
  5. 分析该请求的URL,发现https://api.worldaquatics.com/fina/events/加上events.json中比赛名对应的Id所拼接的新URL就是对应比赛结果的请求URL。根据这一特性,我们得出当需要查询某个比赛的结果时,可以先通过events获取对应比赛结果的Id,之后再通过上述的新URL获取对应比赛结果的json文件
    在这里插入图片描述
3.2 JSON数据的解析

FastJson基于 ASM 的字节码生成和反射优化,使得它在解析和序列化 JSON 数据时非常快速,特别是在处理大型 JSON 数据等需要高性能的场景下。因此我们采用FastJson来进行JSON数据的解析。

  1. 获取FastJson的jar包,使用IDEA导入到lib文件夹
  2. 使用http请求获取json数据,根据数据结构使用fastjson的JSON类将json转为JSONArray或JSONObject类,再根据json结构提取自己需要的数据,将数据映射到实体类
  3. 最后利用StringBuilder来拼接获得的数据用于最后的输出
3.3 input文件中信息的处理
  1. 使用 BufferedReader 来按行读取文件内容。
  2. doCmd函数解析每一行input指令,根据指令内容决定输出错误提示或者调用core中的相关函数获取数据
4. 接口设计和实现过程
4.1 项目结构
├─consts
├─core
│  └─impl
├─model
├─render
└─util
consts:存放常量
core:   存放核心功能接口
model: 存放实体类
impl:  实现接口
render:存放渲染类
utils: 存放工具类
4.2 项目各类与方法
 // 包含main函数,用于调用其他类,并将正确的命令行参数传给其他类
    public class DWASearch{}// 用于文件的读写和input内容的处理
    public class FileRender{}// 用于获取与处理Player与Result的信息的接口
    public interface Search{}

 // 实现Search接口
    public class SearchImpl{}// 用于获取http的请求
    public class HttpUtil{}

在这里插入图片描述

4.3 设计流程图
  • 获取选手列表(getPlayers()):

在这里插入图片描述

  • 获取比赛信息(getResults()):
    在这里插入图片描述
5. 关键代码展示
5.1 Search接口实现
  • 获取选手列表:
    @Override
    public List<Player> getPlayers() {

        if (playerList != null) {
            return playerList;
        }

        try {
            response = HttpUtil.get(Consts.PLAYERS_URL, null, null);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        JSONArray array = JSON.parseArray(response.body);
        List<Player> players = new ArrayList<>();
        for (int i = 0; i < array.size(); i++) {
            JSONObject object = array.getJSONObject(i);
            JSONArray athletes = object.getJSONArray("Participations");
            String country = object.getString("CountryName");
            for (int j = 0; j < athletes.size(); j++) {
                JSONObject athlete = athletes.getJSONObject(j);
                String lastName = athlete.getString("PreferredLastName");
                String firstName = athlete.getString("PreferredFirstName");
                int gender = athlete.getIntValue("gender");
                Player player = new Player(lastName + " " + firstName,
                        gender == 0 ? "Male" : "Female", country);
                players.add(player);
            }
        }
        playerList = players;
        return players;
    }
  • 获取单项比赛记录:
    @Override
    public List<Result> getResults(String eventName) {

        if (resultCache.containsKey(eventName)) {
            Map<String, List<Result>> map = resultCache.get(eventName);
            return map.get(Consts.FINAL_RESULT);
        }

        String eventId = getEventId(eventName);

        try {
            response = HttpUtil.get(Consts.RESULTS_URL + eventId, null, null);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        JSONObject object = JSON.parseObject(response.body);
        JSONArray heats = object.getJSONArray("Heats");
        Map<String, List<Result>> map = new HashMap<>();
        resultCache.put(eventName, map);

        for (int i = 0; i < heats.size(); i++) {
            JSONObject heat = heats.getJSONObject(i);
            String heatName = heat.getString("Name");
            JSONArray results = heat.getJSONArray("Results");
            List<Result> resultList = new ArrayList<>();
            for (int j = 0; j < results.size(); j++) {
                JSONObject result = results.getJSONObject(j);
                String totalPoints = result.getString("TotalPoints");
                String fullName = result.getString("FullName");
                if (fullName.contains("/")) {
                    String[] names = fullName.split(" / ");
                    Arrays.sort(names);
                    fullName = names[0] + " & " + names[1];
                }
                int rank = result.getIntValue("Rank");
                JSONArray dives = result.getJSONArray("Dives");

                String[] scores = new String[dives.size()];
                for (int k = 0; k < dives.size(); k++) {
                    JSONObject dive = dives.getJSONObject(k);
                    scores[k] = dive.getString("DivePoints");
                }
                Result r = new Result(fullName, rank, String.join(" + ", scores) + " = " + totalPoints);
                resultList.add(r);
            }
            map.put(heatName, resultList);
        }
        return map.get(Consts.FINAL_RESULT);
    }
  • 获取详细比赛结果(附加功能):
    @Override
    public List<Map<String, Result>> getDetailedResults(String eventName) {

        if (detailedResultCache.containsKey(eventName)) {
            return detailedResultCache.get(eventName);
        }

        if (!resultCache.containsKey(eventName)) {
            getResults(eventName);
        }

        Map<String, List<Result>> map = resultCache.get(eventName);
        Map<String, Map<String, Result>> flagMap = new HashMap<>();
        List<Map<String, Result>> detailedResults = new ArrayList<>();

        for (String heatName : map.keySet()) {
            List<Result> results = map.get(heatName);
            for (Result result : results) {
                if (flagMap.containsKey(result.getFullName())) {
                    Map<String, Result> resultMap = flagMap.get(result.getFullName());
                    resultMap.put(heatName, result);
                } else {
                    Map<String, Result> resultMap = new HashMap<>();
                    resultMap.put(heatName, result);
                    flagMap.put(result.getFullName(), resultMap);
                    detailedResults.add(resultMap);
                }
            }
        }

        // 排序
        detailedResults.sort((a, b) -> {
            int aFirstRank = getFirstRank(a);
            int bFirstRank = getFirstRank(b);
            return aFirstRank - bFirstRank;
        });

        detailedResultCache.put(eventName, detailedResults);
        return detailedResults;
    }

6. 性能改进
6.1 可改进点的分析

未优化前运行2k+行处理指令的时间:50秒28毫秒

  • 分析点1 :字符串拼接:使用String进行字符串拼接会消耗较长的时间。这是因为在拼接时,它会使用StringBuilder,并调用append方法,最后再调用toString方法。每次拼接都要执行这些操作,导致性能代价较大。
  • 分析点2 :文件读取方式:原先的文件读取方式使用了reader,这种方法进行了大量的I/O操作,并且每次都要将数据先读取为字节,然后进行转码。这种方式效率较低。
  • 分析点3 :重复指令的处理:当输入文件中存在大量重复的指令行时,在编译过程中会处理重复的指令。某些代码可以避免重复执行,从而提高程序的运行效率。
6.2 对应的解决方法

分析点1:使用StringBuilder来拼接字符串,这样不会产生新的对象,减少了频繁创建对象的耗时。

		if (cmd[0].equals("players")) {
            if (cmd.length != 1) {
                return "Error\n-----\n";
            }
            List<Player> players = search.getPlayers();
            StringBuilder builder = new StringBuilder();
            for (Player player : players) {
                builder.append(player.toString()).append("-----\n");
            }
            return builder.toString();
        }

分析点2:直接用BufferedReader来进行文件读取,每次读取一行文件,相对于read方法而言,减少了I/O操作,修改完后会发现速度明显快了许多,看来减少程序的I/O次数在优化过程中是十分必要的。

        BufferedReader reader = new BufferedReader(new InputStreamReader(fins, "UTF-8"));
        while ((lineStr = reader.readLine()) != null) {
            if (!lineStr.equals("")) {
                inputList.add(lineStr.trim());
            }
        }

分析点3:HashMap的查找操作的时间复杂度为O(1),因此可以使用HashMap进行缓存优化,因为它提供了快速的键值对查找和存储操作,可以快速地根据指令查找到对应的输出结果,而无需进行耗时的文件访问和解析操作,于是我们在SearchImpl类中缓存了选手数据与比赛结果数据。

public class SearchImpl implements Search {
    private List<Player> playerList = null;
    private Map<String, String> eventIdMap = null;  //存储比赛名称和对应的id
    private Map<String, Map<String, List<Result>>> resultCache = new HashMap<>();
}

优化后执行2k+行与之前相同的处理指令的运行时间:
在这里插入图片描述

7. 单元测试

本次使用了junit模块,对实现打印选手信息、打印比赛项目信息和详细信息功能的core进行了测试。

7.1 单元测试代码
/**
 * 搜索接口实现测试
 */
public class SearchImplTest {

    private Search search = new SearchImpl();

    @Test
    public void testGetPlayers() {

        search.getPlayers().forEach(System.out::println);
    }

    @Test
    public void testGetResultS() {
        for (String match : Consts.MATCH_ARR) {
            search.getResults(match).forEach(System.out::println);
        }
    }

    @Test
    public void testGetDetailedResults() {
        for (String match : Consts.MATCH_ARR) {
            search.getDetailedResults(match).forEach(System.out::println);
        }
    }
}
7.2 覆盖率测试情况

在这里插入图片描述
代码覆盖率均达到88%,符合单元测试标准

8. 异常处理
  1. 命令行错误处理:
       if (args.length != Consts.ARGS_LENGTH) {
           System.out.println("命令行参数格式错误!应为:Java -jar DWASearch.jar input.txt output.txt");
           System.exit(-1);
       }
  1. input指令异常处理:
        try {
            List<String> inputList = FileRender.getInput(args[0]);
            List<String> dataList = FileRender.getData(inputList);
            out = new BufferedWriter(new FileWriter(args[1]));
            for (String str : dataList) {
                out.write(str);
            }
            out.flush();

        } catch (FileNotFoundException e) {
            System.out.println("找不到指定文件!");
            e.printStackTrace();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (out != null) {
                out.close();
            }
        }
9. 心得体会

在本次软件工程实践作业中,我深刻认识到一个项目的完成远远超出了简单的让代码运行通过。以往的大作业通常只需要提交源代码,而这次的作业让我经历了从规划到实现再到测试编写报告的完整流程,让我意识到了自己在许多方面都不够严谨。

  • 在以往的开发中,我很少接触单元测试这个概念,也从未深入学习过如何编写和执行单元测试。然而,在这次作业中,我不得不学习并实践了单元测试的方法和技巧。通过编写全面、细致的单元测试,我能够更好地了解代码的功能和逻辑,并及时发现和修复潜在的问题。
  • 过去,我很少关注代码的性能问题,只注重功能的实现。然而,在这次作业中,我深刻认识到性能优化对于项目的重要性。通过使用性能测试工具,我能够确定代码中的性能瓶颈,并采取相应的优化措施,以提高代码在大规模数据处理时的效率和响应速度。
  • 自己在时间估计方面存在问题,未能准确评估所需的时间,导致在截止日期临近时才完成所有任务。这给我敲响了警钟,以后我会更加谨慎地评估项目所需的时间,并制定合理的计划,以保证能从容地完成项目。
  • 28
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
东南亚位于我国倡导推进的“一带一路”海陆交汇地带,作为当今全球发展最为迅速的地区之一,近年来区域内生产总值实现了显著且稳定的增长。根据东盟主要经济体公布的最新数据,印度尼西亚2023年国内生产总值(GDP)增长5.05%;越南2023年经济增长5.05%;马来西亚2023年经济增速为3.7%;泰国2023年经济增长1.9%;新加坡2023年经济增长1.1%;柬埔寨2023年经济增速预计为5.6%。 东盟国家在“一带一路”沿线国家中的总体GDP经济规模、贸易总额与国外直接投资均为最大,因此有着举足轻重的地位和作用。当前,东盟与中国已互相成为双方最大的交易伙伴。中国-东盟贸易总额已从2013年的443亿元增长至 2023年合计超逾6.4万亿元,占中国外贸总值的15.4%。在过去20余年中,东盟国家不断在全球多变的格局里面临挑战并寻求机遇。2023东盟国家主要经济体受到国内消费、国外投资、货币政策、旅游业复苏、和大宗商品出口价企稳等方面的提振,经济显现出稳步增长态势和强韧性的潜能。 本调研报告旨在深度挖掘东南亚市场的增长潜力与发展机会,分析东南亚市场竞争态势、销售模式、客户偏好、整体市场营商环境,为国内企业出海开展业务提供客观参考意见。 本文核心内容: 市场空间:全球行业市场空间、东南亚市场发展空间。 竞争态势:全球份额,东南亚市场企业份额。 销售模式:东南亚市场销售模式、本地代理商 客户情况:东南亚本地客户及偏好分析 营商环境:东南亚营商环境分析 本文纳入的企业包括国外及印尼本土企业,以及相关上下游企业等,部分名单 QYResearch是全球知名的大型咨询公司,行业涵盖各高科技行业产业链细分市场,横跨如半导体产业链(半导体设备及零部件、半导体材料、集成电路、制造、封测、分立器件、传感器、光电器件)、光伏产业链(设备、硅料/硅片、电池片、组件、辅料支架、逆变器、电站终端)、新能源汽车产业链(动力电池及材料、电驱电控、汽车半导体/电子、整车、充电桩)、通信产业链(通信系统设备、终端设备、电子元器件、射频前端、光模块、4G/5G/6G、宽带、IoT、数字经济、AI)、先进材料产业链(金属材料、高分子材料、陶瓷材料、纳米材料等)、机械制造产业链(数控机床、工程机械、电气机械、3C自动化、工业机器人、激光、工控、无人机)、食品药品、医疗器械、农业等。邮箱:market@qyresearch.com
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值