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

这个作业属于哪个课程<软件工程23年春季>
这个作业要求在哪里<软件工程实践第二次作业—文件读取>
这个作业的目标
  • Gitcode上传项目
  • 实现统计赛事数据的控制台程序
  • 对程序性能优化
  • 对程序进行测试
  • 撰写博客
其他参考文献《构建之法》、csdn、博客园

1.Gitcode项目地址

👉代码仓库地址

2.PSP表格

PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划3010
• Estimate• 估计这个任务需要多少时间2010
Development开发6001220
• Analysis• 需求分析 (包括学习新技术)120150
• Design Spec• 生成设计文档6030
• Design Review• 设计复审3030
• Coding Standard• 代码规范 (为目前的开发制定合适的规范)3030
• Design• 具体设计6090
• Coding• 具体编码300590
• Code Review• 代码复审4080
• Test• 测试(自我测试,修改代码,提交修改)90210
Reporting报告90120
• Test Repor• 测试报告3080
• Size Measurement• 计算工作量1510
• Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划4530
合计15601350

3.解题思路描述

3.1总体思路

在这里插入图片描述

3.2 json数据获取

使用FireFox的开发者工具获取澳大利亚网球公开赛官网的数据(该爬取行为仅用于课程教学),找到对应的json文件双击后下载到本地。

• players的数据获取

在这里插入图片描述

• results的数据获取

在这里插入图片描述

3.3 解析数据

使用第三方的Gson对本地的json文件进行数据的解析,并使用Map暂存对象数据,List <Map>暂存数组数据。(此前还以为Gson解析都要用bean封装)
参考:Gson实现json与map互相转换

  • 对players解析:
    需要的数据为选手的full_name,gender,nationality,直接获取players数组后,遍历该数组,并且使用get()直接获取对应数据,获取nationality对象后还再获取其name字段。
    在这里插入图片描述
  • 对results解析:
    需要的数据为比赛的time,winner,score,处理流程如下:
    1. 检查比赛状态,若弃赛直接获取addr数据W/O;正常比赛获取如图的time数据,获胜队伍的team_id用于在teams数组中查找获胜队伍;在这里插入图片描述
      2.找到获胜选手的uuid后在players中查找选手,获取winner数据:
      在这里插入图片描述在这里插入图片描述
      3.对应图一的teams中我们可以获得队伍比分情况,获取到score数据:在这里插入图片描述

3.4 文件读写

1.读取input中的指令:readline()一行行遍历input.txt的指令,并将遍历的指存储到一个List中。

2.根据指令List获取对应数据:首先获取指令List,然后遍历指令(此时进行基本的指令正误判断),根据指令获取对应的players与result date数据。

3.将数据写入output文件:获得对应数据后遍历数据写入文件中。

4.接口设计和实现过程

4.1总体接口设计:

主要是两个类,一个AOSearch类(作为入口),一个Utils类(提供接口函数)。

  • 其中analyseResult()调用getWinner()getMatchInfo()getWinnerUuid()等方法来获取比赛结果字符串。
  • checkCmd()方法根据指令,进行判断,返回相应数据;被getData()调用。
    在这里插入图片描述

4.2 算法关键:

  • 利用Gson解析json文件,转化为字符串,其中用Map存储json对象,List<Map>存储json数组。
  • 将输出数据的类型进行判断,对应具体指令输出具体数据,使用正则表达式进行判断。
  • 使用HashMap来缓存,减少频繁的文件读取次数。

4.3主要的接口函数:

//获取json字符串
    public static String getJsonStr(String fileName) throws IOException{...}
    
//Gson解析运动员信息
    public static String analysePlayer() throws IOException{...}
      
//Gson解析比赛结果
    public static String analyseResult(String date) throws IOException{...}
    
//Gson解析比赛结果:获取胜队的选手uuid
    public static List<Map> getWinnerUuid(Map m, List<Map> teams){...} 
    
//Gson解析比赛结果:获取获胜者
    public static String getWinner(List<Map> winnerUuid, List<Map> players){...}  
     
//Gson解析比赛结果:获取双方比赛情况
    public static String getMatchInfo(Map m){...}
    
//获取input文件的指令
    public static List<String> getInput(String fileName) throws IOException{...} 
    
//根据input指令类型获取写入output的数据
    public static List<String> getData(List<String> inputList) throws IOException{...}
     
//判断指令对应的数据内容类型
    public static int checkCmd(String str){...}
     
//把数据写入输出文件
    public static void setOutput(String fileName, List<String> dataList) throws IOException{...}             

5.关键代码展示

  • getJsonStr函数:获取json数据
    • 参数:json文件的相对路径
    • 返回:json字符串
    • 调用关系:被analyseResult()analysePlayer()函数调用;
public static String getJsonStr(String fileName) throws IOException{
        File file = new File(fileName);
        if(!file.exists()){
            throw new FileNotFoundException("目标json文件未找到,请确保路径正确后重试!");
        }
        String lineStr;
        StringBuilder sb = new StringBuilder();
        FileInputStream fins = new FileInputStream(fileName);
        BufferedReader reader = new BufferedReader(new InputStreamReader(fins,"UTF-8"));
        while((lineStr = reader.readLine()) != null){
            sb.append(lineStr);
        }
        reader.close();
        return sb.toString();
    }
  • analysePlayer函数:Gson解析运动员信息
    • 参数:无
    • 返回:解析运动员信息后拼接的字符串
    • 调用关系:调用getJsonStr()获取json数据;
public static String analysePlayer() throws IOException{
        if(hashMap.containsKey("players")){     //使用hashMap缓存数据
            return hashMap.get("players");
        }
        Gson gson = new Gson();
        Map map = gson.fromJson(getJsonStr(playersPath), Map.class);
        List<Map> players = (List<Map>) map.get("players");
        StringBuilder sb = new StringBuilder();
        for(Map p : players){
            sb.append("full_name:" + p.get("full_name") + "\n");
            String gender = p.get("gender").equals("M") ? "male" : "female";
            sb.append("gender:" + gender + "\n");
            sb.append("nationality:" + ((Map)p.get("nationality")).get("name") + "\n");
            sb.append("-----\n");
        }
        hashMap.put("players", sb.toString());
        return sb.toString();
    }
  • analyseResult函数:Gson解析比赛结果
    • 参数:result xx指令的xx(赛程)
    • 返回:解析比赛结果信息后拼接的字符串
    • 调用关系:1.调用getJsonStr()获取json数据;2.调用getWinner()获取胜利选手的缩写名;3.调用getWinnerUuid()获取比赛得分信息。
public static String analyseResult(String date) throws IOException{
        if(hashMap.containsKey(date)){      //使用hashMap缓存数据
            return hashMap.get(date);
        }
        Gson gson = new Gson();
        Map map = gson.fromJson(getJsonStr(resultsPath + date + ".json"), Map.class);
        List<Map> matches = (List<Map>)map.get("matches");
        List<Map> teams = (List<Map>)map.get("teams");
        List<Map> players = (List<Map>)map.get("players");
        StringBuilder sb = new StringBuilder();
        for(Map m : matches){
            Map matchStatus = (Map) m.get("match_status");
            if(matchStatus.get("abbr").equals("W/O")){      //如果参赛状态为弃赛
               sb.append("W/O" + "\n");
            }else{
                sb.append("time:" + m.get("actual_start_time") + "\n");
                sb.append("winner:" + getWinner(getWinnerUuid(m,teams),players) + "\n");
                sb.append("score:" + getMatchInfo(m) + "\n");
            }
            sb.append("-----\n");
        }
        hashMap.put(date, sb.toString());
        return sb.toString();
    }
  • getWinner函数:获取获胜者
    • 参数:1.获胜者选手的uuid;2.players的对象数组
    • 返回:获胜者缩写名的字符串
    • 调用关系:被analyseResult()函数调用;
 public static String getWinner(List<Map> winnerUuid, List<Map> players){
        StringBuilder sb = new StringBuilder();
        List<Map> winners = new ArrayList<>();
        for(int i = 0; i < winnerUuid.size(); i++){
            for(Map p : players){
                if(p.get("uuid").equals(winnerUuid.get(i))){    //1.获取获胜者List
                    winners.add(p);
                }
            }
        }
        for(int j = 0; j < winners.size(); j++){   //2.遍历获胜者名字
            sb.append(winners.get(j).get("short_name"));
            if(winners.size() - j > 1){
                sb.append(" & ");
            }
        }
        return sb.toString();
    }

6.性能改进

6.1 性能分析工具

使用的性能分析工具是Jprofiler11
参考IDEA集成JProfiler11可视化工具

6.2.1 思路一:使用StringBuilder处理字符串

使用StringBuilder来拼接字符串并不会产生新的对象,减少了频繁创建对象的耗时。
参考StringBuffer和StringBuilder区别详解

6.2.2 思路二:使用HashMap缓存

使用HashMap来缓存players和result date数据,这样当重复读取时,不需要频繁的读取文件操作,节省较多的时间。

6.3 优化结果

分别测试未优化前,思路一优化,和思路二优化的代码跑一万条指令的时间,结果如下:
• 未优化:

在这里插入图片描述在这里插入图片描述

• 思路一优化:

优化效果不佳,后来发现是测试数据不够大,拼接次数不足,导致差异不明显。在这里插入图片描述在这里插入图片描述

• 思路二优化:

运行时间大大缩短!在这里插入图片描述在这里插入图片描述

由于此次作业为文件读取,所以主要的优化思路应该放在减少文件读取次数上。

7.单元测试

7.1 测试工具

使用的性能测试工具为Junit5。
参考:1.IDEA中使用JUnit4     2.junit5详解

7.2覆盖率测试与测试样例

一.对AOSearch测试:

 @Test //对命令行参数测试
    public void main() {
        AOSearch.main(new String[]{"input.txt","output.txt"});
        AOSearch.main(new String[]{"input1.txt","output.txt"});
        AOSearch.main(new String[]{"output.txt"});
        AOSearch.main(new String[]{"input.txt"});
        AOSearch.main(new String[]{"input.txt","output.txt","output.txt"});
    }

在这里插入图片描述
二.对Utils部分测试:
1.对获取数据的方法单元测试:

@Test
    public void analyseTest(){
        assertDoesNotThrow(()->Utils.analyseResult("Q1"));
        assertDoesNotThrow(()->Utils.analysePlayer());

        assertThrows(IOException.class, ()->Utils.analyseResult(" "));
        assertThrows(IOException.class, ()->Utils.analyseResult("01116"));
        assertThrows(IOException.class, ()->Utils.analyseResult(" 0116"));
        assertThrows(IOException.class, ()->Utils.analyseResult("0115"));
        assertThrows(IOException.class, ()->Utils.analyseResult("0130"));
        assertThrows(IOException.class, ()->Utils.analyseResult("Qq"));
    }

2.对checkCmd()方法单元测试:

@Test 	//对指令进行测试
    public void checkCmdTest(){
        //正确的指令
        assertEquals(1,Utils.checkCmd("players"));
        assertEquals(2,Utils.checkCmd("result Q1"));
        assertEquals(2,Utils.checkCmd("result 0116"));
        //错误的指令
        assertEquals(-1,Utils.checkCmd("Players"));
        assertEquals(-1,Utils.checkCmd("player"));
        assertEquals(-1,Utils.checkCmd("Result 0116"));
        assertEquals(-1,Utils.checkCmd("resu lt Q1"));
        assertEquals(0,Utils.checkCmd("result"));
        assertEquals(0,Utils.checkCmd("result "));
        assertEquals(0,Utils.checkCmd("result 0116 002"));
        assertEquals(0,Utils.checkCmd("result 0116002"));
        assertEquals(0,Utils.checkCmd("result 016"));
        assertEquals(0,Utils.checkCmd("result 0130"));
        assertEquals(0,Utils.checkCmd("result q1"));
        assertEquals(0,Utils.checkCmd("result       Q 1"));

    }

在这里插入图片描述

8.异常处理

8.1 命令行参数异常

这里处理了命令行参数的异常情况,并对Utils里方法的异常进行捕获。

public static void main(String[] args) {
        Utils U = new Utils();
        try {
            if(args.length != 2){       //命令行参数错误
                throw new IOException("输入参数格式有误,应为:Java -jar AOSearch.jar input.txt output.txt");
            }
            U.setOutput(args[1], U.getData(U.getInput(args[0])));   
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

8.2 JSON文件未找到异常

File file = new File(fileName);
        if(!file.exists()){
            throw new FileNotFoundException("目标json文件未找到,请确保路径正确后重试!");
        }

9.心得体会

  • 一开始以为这次作业只是解析json然后文件读写,但是实际上手后,发现还有许多东西以前没有接触过,比如:单元测试、性能分析等。来来回回参考了很多博客,折腾了也挺久的,好在基本的问题都能够得到解答。
  • 对于时间安排问题,自己以后开始之前还是要正确评估一下到底需要多少时间,以免快到ddl的时候还没写完。
  • 最后,感觉自己这次才对性能优化有了一些了解,对开发者来说优化很重要,对客户更是如此。看来还是需要不断学习拓宽自己的知识面!(ง •_•)ง加油!!
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值