软件工程实践第二次作业——个人实战

222000434杨蕊蘭 学生 2023-03-02 16:52:21
这个作业属于哪个课程2023年福大-软件工程实践-W班
这个作业要求在哪里软件工程实践第二次作业——个人实战
这个作业的目标GitCode项目上传、代码编写并测试、博客撰写
其他参考文献相关链接置于文末

目录

  • 1. Gitcode项目地址
  • 2. PSP表格
  • 3. 解题思路描述
  • 3.1 获取数据
  • 3.2 解析数据
  • 3.3 读取并分析指令
  • 3.4 数据输出
  • 4. 接口设计和实现过程
  • 5. 关键代码展示
  • 5.1 解析json获取实体类对象
  • 5.2 主要流程
  • 5.3 指令判断
  • 5.4 输出winner
  • 6. 性能改进
  • 6.1 优化前
  • 6.2 第一次优化
  • 6.3 第二次优化
  • 6.4 CMD测试效果
  • 7. 单元测试
  • 7.1 部分测试代码
  • 7.2 覆盖率
  • 8. 异常处理
  • 8.1 文件个数异常
  • 8.2 输入文件不存在
  • 8.3 读写异常
  • 9. 心得体会
  • 相关链接

1. Gitcode项目地址

仓库地址:project-java

2. PSP表格

PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划2530
• Estimate• 估计这个任务需要多少时间2530
Development开发9401290
• Analysis• 需求分析 (包括学习新技术)60100
• Design Spec• 生成设计文档2530
• Design Review• 设计复审2010
• Coding Standard• 代码规范 (为目前的开发制定合适的规范)2520
• Design• 具体设计4570
• Coding• 具体编码360600
• Code Review• 代码复审4560
• Test• 测试(自我测试,修改代码,提交修改)360400
Reporting报告115105
• Test Repor• 测试报告4560
• Size Measurement• 计算工作量3015
• Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划4030
合计10801425

3. 解题思路描述

    经过多次阅读题目要求,分析之后得出本次作业可以分为四个部分。首先是从官方网站获取作业所需的数据;二是解析数据,将数据转换为所需要的格式;三是从input文件中读取并分析指令;四是将指令对应的数据写入output文件。

3.1 获取数据

①打开澳大利亚网球公开赛官网,进入开发者模式;

img


②刷新页面请求数据,找到result双击打开,url即为数据地址;

img


③将页面数据保存至本地,修改地址中的数字即可跳转至相应日期的赛程数据。

img

3.2 解析数据

    因为上学期的移动应用开发课程有介绍一些json的相关知识,所以选择用gson解析数据。一开始观察数据格式,发现数据量实在是太大了,看的很头疼,不知道应该如何设计实体类,所以选择通过在线转换工具,转换出Javabean,再选择需要的属性,嵌套生成对应的players和schedule类,在项目中导入gson的jar包即可使用。
    部分转换效果如图:

img

3.3 读取并分析指令

    使用BufferedReader对input文件进行读取,每读取一行就对指令进行相应的判断,流程图如下:

img

3.4 数据输出

    对于players可以使用解析数据得到的实体类的get方法得到相应的full_name、gender、nationality,使用BufferedWriter的write方法向output文件写入对应格式的数据。
    对于赛程数据的输出,首先需要判断该场比赛是否正常进行,还是存在弃赛情况,若比赛正常进行,由于JSON数据的结构并不是直接对应我们需要的数据,需要通过获胜队伍的id在teams列表中找到对应的队伍,获取其包含的队员的id,再从players列表中查找对应的player,得到short_name;弃赛则输出W/O。

img


img


img

4. 接口设计和实现过程

    首先是JSON数据解析所需要的两个实体类,其内部结构必须与数据结构相同,正确嵌套,对应的名称也要一样。

//省略对应的get、set函数
public class Json2players {
    private List<Players> players;
    public static class Players {
        private String uuid;
        private String full_name;
        private String gender;
        private Nationality nationality;

        public static class Nationality {

            private String name;
        }
    }
}
//省略对应的get、set函数
public class Json2schedule {
    private List<Matches> matches;
    private List<Players> players;
    private List<Teams> teams;
    public static class Matches {
        private String actual_start_time;
        private List<Teams> teams;
        private Match_status match_status;
        public static class Teams {
            private String team_id;
            private List<Score> score;
            private String status;
            public static class Score {
                private String game;
            }
        }

        public static class Match_status {
            private String abbr;
        }
    }

    public static class Players {
        private String uuid;
        private String short_name;
    }

    public static class Teams {
        private String uuid;
        private List<String> players;
    }
}

    AOSearch类作为程序的入口,进行相应的文件异常的处理,以及程序执行流程的条件判断,调用Lib类中相应的功能函数。
    Lib类中包括以下功能函数

//team_id对应player缩写list
public static HashMap<String, List<String>> hashMap=new HashMap<>();
//对player.json进行解析,返回Json2players类对象
public static Json2players analyzeplayers() {...}

//date为指令中的对应日期字符串
//对date.json进行解析,返回Json2schedule类对象
public static Json2schedule analyzeschedule(String date) {...}

//比赛情况输出流程
public static void output(StringBuilder stringBuilder, Json2schedule jsonschedule) throws IOException {...}

//匹配对应获胜队伍的队员缩写
//输出winner
public static  void output_winner(StringBuilder stringBuilder, Json2schedule.Matches matches, Json2schedule jsonschedule) {...}

//输出score
public  static void output_score(StringBuilder stringBuilder, Json2schedule.Matches matches) {...}

//按格式输出player
public static void output_players(StringBuilder stringBuilder, Json2players jsonplayers) throws IOException {...}

//判断指令
public static String analyzeinstruct(String instruct) {...}

5. 关键代码展示

5.1 解析json获取实体类对象

//对player.json进行解析,返回Json2players类对象
public static Json2players analyzeplayers() {
    String path="./src/data/players.json";
    BufferedReader br=null;
    try {
        br=new BufferedReader(new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8));
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    Gson gson=new Gson();
    Type type=new TypeToken<Json2players>() {}.getType();
    Json2players json_players=gson.fromJson(br,type);
    try {
        br.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return json_players;
}

5.2 主要流程

String instruct;
while ((instruct=reader.readLine())!=null) {
    instruct.trim();
    StringBuilder sb=new StringBuilder();
    //判断指令类型
    String str=Lib.analyzeinstruct(instruct);
    if (!"null".equals(str)) {//跳过空行
        if (!str.equals("N/A")&&!str.equals("Error")) {
            if (hashMap.containsKey(str)) {
                stringBuilder.append(hashMap.get(str));
            }
            else {
                if (str.equals("players")) Lib.output_players(sb,Lib.analyzeplayers());
                else Lib.output(sb,Lib.analyzeschedule(str));
                hashMap.put(str,sb);
                stringBuilder.append(sb);
            }
        }
        else stringBuilder.append(str).append("\n-----\n");
    }
    count++;
    if (count==10000) {
        writer.write(String.valueOf(stringBuilder));
        stringBuilder.setLength(0);
        count=0;
    }
}
writer.write(String.valueOf(stringBuilder));

5.3 指令判断

//判断指令
public static String analyzeinstruct(String instruct) {
    String str = "";
    if (instruct.equals("")) str="null";//空行
    else {
        String[] list=instruct.split("\\s+");
        if (list.length==0) str="null";
        else if ("result".equals(list[0])) {
            if (list.length==2&&(list[1].equals("0116")||list[1].equals("0117")||list[1].equals("0118")||list[1].equals("0119")||list[1].equals("0120")
                    ||list[1].equals("0121")||list[1].equals("0122")||list[1].equals("0123")||list[1].equals("0124")||list[1].equals("0125")||list[1].equals("0126")
                    ||list[1].equals("0127")||list[1].equals("0128")||list[1].equals("0129")||list[1].equals("Q1")||list[1].equals("Q2")||list[1].equals("Q3")||list[1].equals("Q4"))) str=list[1];
            else str="N/A";
        }
        else if ("players".equals(list[0])) {
            if (list.length==1) str="players";
            else str="Error";
        }
        else if (list[0].equals("")) {
            if ("result".equals(list[1])) {
                if (list.length==3&&(list[2].equals("0116")||list[2].equals("0117")||list[2].equals("0118")||list[2].equals("0119")||list[2].equals("0120")
                        ||list[2].equals("0121")||list[2].equals("0122")||list[2].equals("0123")||list[2].equals("0124")||list[2].equals("0125")||list[2].equals("0126")
                        ||list[2].equals("0127")||list[2].equals("0128")||list[2].equals("0129")||list[2].equals("Q1")||list[2].equals("Q2")||list[2].equals("Q3")||list[2].equals("Q4"))) str=list[2];
                else str="N/A";
            }
            else if ("players".equals(list[1])) {
                if (list.length==2) str="players";
                else str="Error";
            }
        }
        else str="Error";
    }
    return str;
}

5.4 输出winner

//获取获胜队伍id
if ("Winner".equals(matches.getTeams().get(0).getStatus()))
    id=matches.getTeams().get(0).getTeam_id();
else id=matches.getTeams().get(1).getTeam_id();
//根据队伍id匹配队伍
//访问过的队伍数据直接从hashmap中获取
//未访问过的通过json数据得到,并放入hashmap
List<String> list=new ArrayList<>();
if (hashMap.containsKey(id)) {
    list = hashMap.get(id);
    for (int i = 0; i < list.size(); i++) {
        str.append(list.get(i));
        if (i != list.size() - 1) str.append(" & ");
    }
}
else {
    for (int j=0;j<jsonschedule.getTeams().size();j++) {
        if (id.equals(jsonschedule.getTeams().get(j).getUuid())) {
            for (int k=0;k<jsonschedule.getTeams().get(j).getPlayers().size();k++) {
                String playerid=jsonschedule.getTeams().get(j).getPlayers().get(k);
                //根据playerid匹配short_name
                for (int l=0;l<jsonschedule.getPlayers().size();l++) {
                    if (playerid.equals(jsonschedule.getPlayers().get(l).getUuid())) {
                        str.append(jsonschedule.getPlayers().get(l).getShort_name());
                        list.add(jsonschedule.getPlayers().get(l).getShort_name());
                        break;
                    }
                }
                if (k!=jsonschedule.getTeams().get(j).getPlayers().size()-1) str.append(" & ");
            }
            break;
        }
    }
    hashMap.put(id,list);
}

6. 性能改进

6.1 优化前

    一开始没有使用StringBuilder记录需要往output文件内写入的内容,最后一次性写入,而是一边判断,执行相应的获取数据功能,一边向文件写入,当写入的数据量很大时,有多少行数据,就要写入多少次,十分耗时;并且还存在每读取一条有效指令,就要打开、关闭一次json数据文件,并且使用Gson进行解析,也是十分耗时的操作。优化前执行32万行有效指令耗时如图:

img

6.2 第一次优化

    使用StringBuilder记录需要写入的内容,每次只需要使用append()方法加入,最后只需要向文件写入一次。使用HashMap记录日期与对应的结果,每次读取到有效指令时先判断是否在HashMap中,存在可直接取出对应键值加入StringBuilder中,不存在则执行相应的获取数据的功能函数,并加入HashMAp。第一次优化执行3万行有效指令耗时如图:

img


当执行30万行有效指令时,程序报错:Java heap space,内存溢出,我认为是因为StringBuilder过大。

6.3 第二次优化

    再次使用HashMap,用于从team_id匹配到player的short_name,每次查找时,先判断是否在HashMap中,存在则取出缩写的list,不存在则进行解析查找,最后加入HashMap。因为StringBuilder过大,所以增加一个计数的count变量,每读取解析10000条指令,就向output文件输入一次,使用将StringBuilder的length置零的方法清除数据。第二次优化执行3万行有效指令耗时如图:

img


第二次优化执行32万行有效指令耗时如图,文件大小2.04G:

img


    虽然第二次优化执行3万行有效指令的速度和第一次优化差不多,但是解决了内存溢出问题,并且从优化前到第二次优化后,速度快了95倍!

6.4 CMD测试效果

    在CMD中测试32万条有效指令,运行时间会比直接运行源程序慢3秒左右。

img

7. 单元测试

7.1 部分测试代码

  • 对分析指令类型进行测试
    @Test
    public void analyzeinstruct() {
      assertEquals("Error",Lib.analyzeinstruct("12345"));
      assertEquals("Error",Lib.analyzeinstruct("resu"));
      assertEquals("Error",Lib.analyzeinstruct("resultplayer"));
      assertEquals("Error",Lib.analyzeinstruct("pla yers"));
      assertEquals("Error",Lib.analyzeinstruct("re sult0120"));
      assertEquals("Error",Lib.analyzeinstruct("result0116"));
      assertEquals("Error",Lib.analyzeinstruct("player"));
      assertEquals("Error",Lib.analyzeinstruct("players  2"));
      assertEquals("Error",Lib.analyzeinstruct("  players  11"));
      assertEquals("N/A",Lib.analyzeinstruct("   result   0116  23"));
      assertEquals("N/A",Lib.analyzeinstruct("result Q 01"));
      assertEquals("N/A",Lib.analyzeinstruct("result 01166"));
      assertEquals("N/A",Lib.analyzeinstruct("result 0155"));
      assertEquals("N/A",Lib.analyzeinstruct("result q"));
      assertEquals("N/A",Lib.analyzeinstruct("result"));
      assertEquals("null",Lib.analyzeinstruct(""));
      assertEquals("null",Lib.analyzeinstruct("       "));
      assertEquals("Q4",Lib.analyzeinstruct("result Q4"));
      assertEquals("0116",Lib.analyzeinstruct("result 0116"));
      assertEquals("0116",Lib.analyzeinstruct("result   0116"));
      assertEquals("0129",Lib.analyzeinstruct("   result   0129"));
      assertEquals("players",Lib.analyzeinstruct("players"));
      assertEquals("players",Lib.analyzeinstruct("players  "));
      assertEquals("players",Lib.analyzeinstruct("  players  "));
    }
    
  • 对解析数据进行测试
    @Test
    public void analyzeschedule() {
      assertNotNull(Lib.analyzeschedule("0116"));
      assertNotNull(Lib.analyzeschedule("0117"));
      assertNotNull(Lib.analyzeschedule("0118"));
      assertNotNull(Lib.analyzeschedule("0119"));
      assertNotNull(Lib.analyzeschedule("0120"));
      assertNotNull(Lib.analyzeschedule("0121"));
      assertNotNull(Lib.analyzeschedule("0122"));
      assertNotNull(Lib.analyzeschedule("0123"));
      assertNotNull(Lib.analyzeschedule("Q1"));
      assertNotNull(Lib.analyzeschedule("Q2"));
      assertNotNull(Lib.analyzeschedule("Q3"));
    }
    

7.2 覆盖率

只对Lib类中的功能函数进行测试

img

8. 异常处理

8.1 文件个数异常

if (args.length!=2) {
    System.out.println("输入的文件个数有误!");
    System.exit(0);
}

8.2 输入文件不存在

if (!input.exists()) {
    System.out.println("未找到该输入文件!请检查您的文件路径:" + args[0]);
    System.exit(0);
}

8.3 读写异常

try {
    BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(args[0]), StandardCharsets.UTF_8));
    BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(args[1]), StandardCharsets.UTF_8));
    stringBuilder= new StringBuilder();
    hashMap= new HashMap<>();
    try {
        String instruct;
        while ((instruct=reader.readLine())!=null) {...}
        writer.write(String.valueOf(stringBuilder));
        reader.close();
        writer.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
}

9. 心得体会

    首先是合理安排时间真的非常重要!因为疫情原因这学期开学还需要进行上学期一些课程的缓考,而且还有本学期课程要上,导致时间非常紧,考试和作业都很重要,只能疯狂挤时间😭😭😭。
    然后是要努力提高专注力和效率,这样才能深入地去思考和设计如何实现这些功能,避免磕磕绊绊地编程,保证程序的质量同时节约时间。
    再是心态很重要!面对这么多事情的压力,我很担心自己没有办法处理好它们,一开始看到这次实战的项目要求,我的内心是抗拒的,因为涉及到的知识点、方法还有整个设计过程都是我不曾接触、学习的,特别是当我看到庞大的json数据并且担心gson没有办法成功解析的时候,我整个人都非常的消极。所以我觉得保持良好的心态是十分重要的,遇到困难害怕是正常的,但是要有迎难而上的心,而不是知难而退。
    最后是多学一点很重要,学无止境,会的越多,在遇到困难的时候能解决的办法就越多,就比如说在解析数据的时候,如果我会使用其他方法解析json数据,那么也就不会担心gson是否能成功,在面对项目要求的时候,如果我学习过性能改进、单元测试这些知识的话,心态是否就大不相同了呢?
    在本次作业中,对JSON数据的处理,文件的使用,性能分析以及单元测试等,对我来说都是比较困难的点,特别是性能分析和单元测试,希望在之后的课程能进一步地学习如何设计测试数据,使测试更加有效。

相关链接

【Java】Map集合概述
Java使用GSON对JSON进行解析
保姆级教程——IDEA中使用Junit进行测试
[JAVA] java: 错误: 无效的源发行版:17

...全文
913 2 打赏 收藏 举报
写回复
用AI写文章
2 条回复
切换为时间正序
请发表友善的回复…
发表回复
  • 打赏
  • 举报
回复 1

完成度很高,赞!

虽然第二次优化执行3万行有效指令的速度和第一次优化差不多,但是解决了内存溢出问题,并且从优化前到第二次优化后,速度快了95倍!

整个优化过程有思路有数据支持,很好地体现优化过程,赞!
勇敢走出舒适圈,不断挑战自己的上限,就会发现事情并没有那么困难。加油!!!

222000434杨蕊蘭 学生 2023-03-22
  • 举报
回复
@2023年福大-软件工程实践-W班 谢谢老师!我会继续努力的
内容概要:WiFiSupply斯普莱WFS7000BI是一款高性能、电信工业级的室外型大功率无线基站,支持5.8GHz全频段(4920-6100MHz),基于MIMO 2T2R架构,最大发射功率达30dBm/1000mw,无线带宽最高300Mbps,点对点实际吞吐量超过150Mbps,支持TDMA时分多址技术,通信延迟≤1ms,具备强抗干扰能力和超远传输距离(桥接可达150公里以上)。设备集成16dBi双极化扇区天线,支持快速无缝漫游、多SSID、VLAN、QoS、VPN、防火墙、巨量安全隧道等多种网络功能,采用IP68防护等级和宽温设计(-45~85℃),具备防浪涌、防静电、电磁屏蔽等工业级防护特性,适用于复杂环境下的无线桥接与覆盖。支持胖瘦AP模式、本地及云端管理,并提供API接口实现第三方平台集成。; 适合人群:从事工业无线网络建设、系统集成、智能交通、智慧物流等领域的网络工程师、技术负责人及项目实施人员。; 使用场景及目标:适用于智慧交通、工业4.0、AMR/AGV小车组网、无人机遥控、车载船载视频传输、驾驶员考试系统、森林防火监控等对稳定性、低延迟、远距离通信要求高的室内外无线通信场景,目标是构建高可靠、高安全、长距离的无线网络传输系统。; 阅读建议:本文档技术参数详实,建议结合具体项目需求重点查阅频段、功率、组网模式、防护等级及管理方式等内容,便于选型与部署。
内容概要:WiFiSupply斯普莱WFS7000-52AX5是一款高性能、高负载型工业级双射频无线AP基站,支持2.4G和5.8G双频段WiFi6(802.11ax)标准,采用OFDMA、MIMO 4T4R架构及射频合路技术,实现最大1974Mbps的双频合并带宽。设备具备29dBm高发射功率、-96dBm高接收灵敏度,支持Fast Roaming快速漫游(切换延迟≤30ms)、多链路聚合、主备链路备份、巨量安全隧道等核心技术,保障低延迟、零丢包的稳定通信。配备5个千兆网口(含1个2.5G口)、支持POE/DC/端子多种供电方式,具备IP54防护、-45~85℃宽温运行、防浪涌防静电等工业级设计,适用于复杂恶劣环境。支持胖瘦一体模式、云端/本地/图形化多方式管理,并提供API接口便于第三方平台集成。; 适合人群:工业自动化、智能制造、智慧物流等领域中从事无线网络部署与运维的工程师和技术人员,具备基本网络知识和工业通信需求的专业用户。; 使用场景及目标:①用于AMR/AGV/RGV小车、无人叉车、穿梭车、天车、堆垛机等移动设备的无线联网;②满足无人机遥控、车载、船载、火车视频传输、分拣机PLC控制等高移动性、高可靠性场景的无线桥接与覆盖需求;③实现工业4.0环境下大规模终端接入、无缝漫游、安全传输的无线网络建设。; 阅读建议:本文档技术参数详尽,建议结合实际部署场景重点关注设备的射频性能、漫游切换、供电安装方式及管理功能,以便充分发挥其在高负载、强干扰工业环境下的优势。
内容概要:本文提出了一种基于混沌系统与DNA编码的彩色图像加密解密方案,旨在提升图像传输的安全性与抗干扰能力。通过结合混沌系统的初值敏感性、伪随机性和遍历性,以及DNA编码的高维信息存储与并行处理优势,实现对彩色图像RGB三通道的多层级加密。方案涵盖图像预处理、混沌序列生成、像素置乱、DNA编码扩散和通道融合等关键步骤,构建了复杂的双重混淆扩散机制。同时,系统开展了抗噪声与抗裁剪性能分析,验证了算法在高斯噪声、椒盐噪声干扰及不同面积、位置裁剪下的鲁棒性,结果表明解密图像仍能保持主体结构与核心信息的完整性。; 适合人群:具备一定信息安全与图像处理基础知识,从事信息隐藏、多媒体安全或密码学相关研究的研究生及科研人员。; 使用场景及目标:①应用于数字图像的安全传输与存储,如医疗影像、军事通信等高安全需求领域;②用于研究图像加密算法的抗攻击能力与鲁棒性机制;③为混沌系统与生物编码技术在信息安全中的融合应用提供理论支持与实践参考。; 阅读建议:建议结合Matlab代码实现对加密流程进行仿真复现,重点关注混沌参数设置、DNA编码规则选择对加密效果的影响,并通过添加不同类型与强度的噪声、裁剪攻击来测试算法鲁棒性,深入理解其安全机制与优化方向。

688

社区成员

发帖
与我相关
我的任务
社区描述
2023年福州大学软件工程实践课程W班的教学社区
软件工程团队开发软件构建 高校 福建省·福州市
社区管理员
  • FZU_SE_teacherW
  • 张书旖
  • 郭渊伟
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧