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

这个作业属于哪个课程<软件工程-23年春季学期>
这个作业要求在哪里<软件工程实践第二次作业—文件读取>
这个作业的目标<完成对澳大利亚网球公开赛相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序>
其他参考文献《构建之法》《源代码管理》

目录:

0.Gitcode项目地址

  1. PSP表格
  2. 解题思路描述
    1. 从相关网址获取json
    2. json解析
    3. 数据提取分析
  3. 接口设计和实现过程
    1. 接口设计
    2. 接口实现
  4. 关键代码展示
  5. 性能改进
    1. 分析
    2. 改进
  6. 单元测试
  7. 异常处理
  8. 心得体会

0.Gitcode项目地址

仓库地址

1. PSP表格

PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划1010
• Estimate• 估计这个任务需要多少时间1010
Development开发600500
• Analysis• 需求分析(包括学习新技术)120100
• Design Spec• 生成设计文档2050
• Design Review• 设计复审6060
• Coding Standard• 代码规范 (为目前的开发制定合适的规范)2030
• Design• 具体设计12060
• Coding• 具体编码360290
• Code Review• 代码复审12090
• Test• 测试(自我测试,修改代码,提交修改)120120
Reporting报告210165
• Test Repor• 测试报告8060
• Size Measurement• 计算工作量6060
• Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划6060
合计19701665

2. 解题思路描述

2. 1 从相关网址获取json

​ 1、在这里插入图片描述

​ 2、在这里插入图片描述

​ 3、在这里插入图片描述

​ 4、在这里插入图片描述

2.2 json数据解析

在这里插入图片描述

用vs打开压缩过的json数据,然后点击格式化

players.json的数据可以看成上面这几个部分,而我们主要需要提取的是players这一项

点开players这一项

查看

发现
需要的数据都在里面

构成json-》players-》full_name / nationality->name /gender的树形结构,代码即可从这里入手

接着分析result date的json数据

总体分为

这样的一个结构

我们需要的数据在

点开matchs

看到我们需要的数据[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.3、数据提取分析

根据需求我们需要获得winner的名字,但是根据json文件我们只能获得team_id

如图

所以需要查看该文件内teams数组内的内容

如图

再由玩家hash值去该文件的players去查找

如图

代码编写也是按照如上思路去进行

3. 接口设计和实现过程

3.1接口设计

由需求可知需要将打印全部玩家与打印全部比赛结果提取出来,所以考虑使用java8的新属性,可在接口中使用静态方法,继承者可以直接调用接口中的静态方法

  • 第一步需要实现一个静态的writePlayers的方法,内部用阿里巴巴的fastjson去解析json数据,并且根据题目要求打印
  • 第二部需要实现静态的writeResults的方法,需要实现searchTeam与searchWinner的方法,去查找胜利的队伍内对应玩家的简称
  • 第三步提取输入内容的过程实现writeInfo函数,用于随时向output.txt输入内容
3.2实现过程
  • 由上述分析可知,需要先导入阿里巴巴的fastjson解析包,调用fastjson的jsonObject与jsonArray去解析对应数据

  • 然后调用内部api实现searchTeam与searchWinner的方法,去查找胜利的队伍内对应玩家的简称的关键代码

  • writePlayers函数与writeResults函数只需要调用对应api即可

4. 关键代码展示

  • 实现searchTeam与searchWinner的方法,去查找胜利的队伍内对应玩家的简称的关键代码
 //通过uuid查找队伍
     static JSONObject searchTeam(String teamUuid,JSONArray teams){
        for (int i = 0;i < teams.size();i++){
            if (teams.getJSONObject(i).getString("uuid").equals(teamUuid)){
                return teams.getJSONObject(i);
            }
        }
        return null;
    }

    //通过uuid数组查找选手
     static JSONArray searchWinners(String playerUuids,JSONArray players){
        JSONArray winners = new JSONArray();
        JSONArray uuids = JSONArray.parseArray(playerUuids);//需要查找的选手uuid
        for (int i = 0; i < uuids.size(); i++){
            String uuid = uuids.getString(i);
            for (int j = 0; j < players.size(); j++){
                if (players.getJSONObject(j).getString("uuid").equals(uuid)){
                    winners.add(players.getJSONObject(j));
                }
            }
        }
        return winners;
    }
  • 调用api实现打印全部选手的函数、
     //对应接口中的writePlayers代码如下
     static void writePlayers(String outputFile) throws Exception {
      if (cache.containsKey("players")) {
        // 重复请求
        writeInfo(cache.get("players"),outputFile);
        return;
      }
        JSONObject jsonObject = JSONObject.parseObject(getJSONStr(PLAYERS_FILES));
        JSONArray players = JSONArray.parseArray(jsonObject.getString("players"));//获取运动员json数组
        StringBuilder playersInfo = new StringBuilder();//遍历运动员数组,把相关信息加入文件输出
        for (int i = 0; i < players.size(); i++){
            JSONObject player = players.getJSONObject(i);
            playersInfo.append("full_name:" + player.getString("full_name") + "\n");
            playersInfo.append("gender:" + ("F".equals(player.getString("gender"))? "Female" : "Male") + "\n");
            playersInfo.append("nationality:" + player.getJSONObject("nationality").getString("name") + "\n");
            playersInfo.append("-----\n");
        }
        cache.put("players",playersInfo.toString());//第一次读取的时候
        writeInfo(playersInfo.toString(),outputFile);
    }

5. 性能改进

5.1 分析
  • 性能的优化主要在于I/O流的重复启动。例如如果输入的指令有重复的,则I/O流需要重复操作,不断重复代码的运行,代码运行的重复率较高
  • 输入流用FileWriter比较慢
5.2 改进
  • 使用hashMap存储key与data充当缓存机制的实现体,key对应的是players这样的命令,而data对应的是需要读取进输出文件的内容。这样每次读取指令先对hashMap中的指令进行查找,如果查找到了,则将hashMap的内容输出到输出文化,减少了代码运行的计算时间
HashMap<String,String> cache = new HashMap<>();//用于模拟缓存,反正指令重复导致的反复读写IO
 //每次读取前进行判断
if (cache.containsKey("players")) {
        // 重复请求
        writeInfo(cache.get("players"),outputFile);
        return;
}
      
cache.put("players",playersInfo.toString());//第一次读取的时候
//每次读取前进行判断
if(cache.containsKey(date)){
          writeInfo(cache.get(date),outputFile);
            return;
}
cache.put(date,resultsInfo.toString());
  • 使用BufferdWriter包装FileWriter去对输出流进行控制
//优化前
FileWriter fileWriter = new FileWriter(outputFile,true);
fileWriter.write(msg);
fileWriter.close();

//优化后
BufferedWriter bw = new BufferedWriter(new FileWriter(outputFile,true));
bw.write(msg);
bw.close();

对如图

这样测试之后改进前的程序运行时间为:

这样测试之后改进后的程序运行时间为:

速度快了将近500ms,速度快了将近5/7

6. 单元测试

测试采用java的Juit进行测试,在对应函数上写入@Test注释并按alt+enter进行maven依赖的导入

  • 对AOSearch.java的单元测试(主要对参数不同,与参数错误进行测试)
 @Test
  public void test1(){
    //错误的参数输入
    AOSearch.main(new String[]{"input.txt"});
  }
  @Test
  public void test1_1(){
    //错误的参数输入
    AOSearch.main(new String[]{"input"});
  }
  @Test
  public void test2(){
    //正确的参数个数输入
    AOSearch.main(new String[]{"input.txt","output.txt"});
  }
  @Test
  public void test2_1(){
    //正确的参数个数输入
    AOSearch.main(new String[]{"input.txt","put.txt"});
  }
  @Test
  public void test2_2(){
    //错误的参数输入,结果可以输出put文件,可正常写入,且可以用记事本查看
    AOSearch.main(new String[]{"input.txt","put"});
  }
  @Test
  public void test2_3(){
    //错误的参数输入结果可以输出put.p文件,可正常写入,且可以用记事本查看
    AOSearch.main(new String[]{"input.txt","put.p"});
  }
  @Test
  public void test3(){
    //不正确的参数输入,覆盖率较高,程序运行较为成功
    AOSearch.main(new String[]{"input.txt","output.txt","others.txt"});
  }
  @Test
  public void test3_1(){
    //不正确的参数输入,覆盖率较高
    AOSearch.main(new String[]{"input.txt","output","others.txt"});
  }

测试结果

1、对test1的测试

结果分析:class类的覆盖率50是因为,包中的测试类未实现,而方法覆盖率低是因为穿啊如的参数错误,代码执行的少,程序结束的快

2、对test1_1的测试

3、对test2的测试

4、对test2_1的测试

5、对test2_2的测试

6、对test2_3的测试

结果符合预期

  • 对Lib接口的测试(主要对,各种不一样参数的输入进行判断)
 @Test
  public void test1() {
    try{
      Lib.writePlayers("output.txt");
      Lib.writeInfo("aa","output.txt");
      Lib.writeResults("0116","output.txt");
      Lib.writeResults("0130","output.txt");
    }catch (Exception e){
      e.printStackTrace();
    }
  }

  @Test
  public void test1_1() {
    try{
      Lib.writePlayers("output");
      Lib.writePlayers("output.pp");
    }catch (Exception e){
      e.printStackTrace();
    }
  }

  @Test
  public void test1_2() {
    try{
      Lib.writeResults("Q55","output");
      Lib.writeResults("Q55","out.txt");
      Lib.writeResults("","out.txt");
    }catch (Exception e){
      e.printStackTrace();
    }
  }
  @Test
  public void test1_3() {
    try{
      Lib.writeResults("-11","out.txt");
      Lib.writeResults("-11","ahs");

    }catch (Exception e){
      e.printStackTrace();
    }
  }
  • 对test1的测试结果

  • 对test1_1的测试结果

  • 对test1_2的测试结果

  • 对test1_3的测试结果

总结:类覆盖率达到100%,是因为所以包中的类都对其进行了实现,方法覆盖率无法进一步提升是因为io流的限制

  • 实现自动单元测试

1、

2、

3、

4、挑选需要进行测试的member

7. 异常处理

  • 总体使用try…catch…finally对代码进行维护

public class AOSearch implements Lib{
    public static void main(String[] args) {
        //毫秒ms:
        long startTime=System.currentTimeMillis(); //获取开始时间
       ...
        try{...}
        }catch(Exception e){
            e.printStackTrace();
            System.out.println("错误");
        }finally {
            long endTime=System.currentTimeMillis(); //获取结束时间
            System.out.println("程序运行时间: "+(endTime-startTime)+"ms");
        }
    }
}
  • 还有抛出异常,返回给调用者处理
  • 例如:
static String getJSONStr(String fileName) throws IOException{
        File file = new File(fileName);
        if (file.exists()){
            //检查json文件是否存在
            FileInputStream fileInputStream = new FileInputStream(file);
            int length = fileInputStream.available();
            byte[] bytes = new byte[length];
            fileInputStream.read(bytes);
            String JSONStr = new String(bytes);//从文件中获取json字符串
            fileInputStream.close();
            return JSONStr;
        }else{
            throw new FileNotFoundException("未找到" + fileName);
        }
    }

8. 心得体会

这次作业主要的难点在于对于json数据的解析、单元测试的编写、性能的提升、还有打包文件的过程。打包文件为jar包每次都会碰到奇怪的问题,要花大量时间去处理。分析题目不难,难在对于输入各种异常结果的输出测试,文件结构相对简单,但是编写过程会遇到杂七杂八的问题需要查找与解决。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 第2关要求我们学习如何使用hdfs-java接口来读取文件。 Hadoop分布式文件系统(HDFS)是Hadoop的核心组件之一,它提供了一种可靠的、高容错性的分布式文件系统,可以存储大量的数据。而hdfs-java接口则是Hadoop提供的Java API,可以用来操作HDFS。 在这一关中,我们需要学习如何使用hdfs-java接口来读取HDFS中的文件具体来说,我们需要掌握如何创建一个HDFS文件系统对象、如何打开一个HDFS文件、如何读取文件内容等操作。 通过学习这些内容,我们可以更好地理解Hadoop分布式文件系统的工作原理,也可以更加灵活地使用Hadoop来处理大数据。 ### 回答2: HDFS(Hadoop 分布式文件系统)是开源框架 Apache Hadoop 中的主要组件之一。它设计用来运行在大规模的硬件集群上,可提供容错性,高可用性和高吞吐量的数据访问。 在 HDFS 中,每个文件都分散存储在多个机器上,这些机器成为数据节点(DataNode),其中一个 Namenode 协调这些数据节点并决定文件存储的位置。HDFS 采用副本机制保障数据的可靠性,每个文件默认有三个副本。因此,在文件读取时,可以从任何一个副本节点中读取文件内容,这样可以提供数据读取的高可用性和冗余性。 HDFS 提供了 Java API 以实现读取和写入文件,这个接口称为 Hadoop HDFS Java API。这个 API 提供一系列类和接口供我们使用。例如,FileSystem、Path 和 FSDataInputStream 类可以用来打开和读取 HDFS 中的文件。其中 FileSystem 是用于访问 HDFS 中文件系统的核心类,它提供了诸如 create()、delete()、mkdir() 等操作访问文件系统。Path 类表示文件在 HDFS 中的路径,FSDataInputStream 则用于读取 HDFS 中的文件。 hdfs-read-file 示例代码: ```java import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.*; import java.io.*; public class HdfsReadFile{ public static void main(String[] args) throws Exception{ String uri = "hdfs://localhost:9000/test/file.txt"; Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(URI.create(uri), conf); Path path = new Path(uri); FSDataInputStream in = fs.open(path); BufferedReader d = new BufferedReader(new InputStreamReader(in)); String line; while ((line = d.readLine()) != null) { System.out.println(line); } d.close(); fs.close(); } } ``` 这个程序从 HDFS 读取指定文件并输出文件的内容。其中,FileSystem.get() 方法获取以用户身份访问 HDFS 的 FileSystem 实例,Path 类维护了文件路径信息,而 FSDataInputStream 类提供了读取文件的方法。 总之,Hadoop HDFS Java API 为我们提供了开发 Hadoop 应用程序的高级接口,使得我们能够轻松地访问 HDFS,实现各种操作,例如读取、写入和删除文件等。本关卡介绍了 HDFS 文件读取,通过实现这个简单的程序,我们了解到 HDFS Java 接口的小部分。 ### 回答3: HDFS(Hadoop分布式文件系统)是Hadoop中非常重要的一个组件,它能够将海量的数据以高容错性,高吞吐量的方式存储在多个节点上,实现了对大数据的高效处理。在Java应用中,读取HDFS数据需要使用Hadoop提供的Java API来进行操作。 在读取HDFS文件之前,需要先建立连接到Hadoop集群的配置信息。建立连接的代码如下: ```java Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(new URI("hdfs://localhost:9000"), conf, "hadoop"); ``` 在这,`conf`是一个`Configuration`对象,表示Hadoop集群的一些配置信息;`FileSystem`是Hadoop提供的管理文件系统的类,其中的`URI`参数是HDFS所在集群的网络地址,`conf`表示连接HDFS所需的配置信息,最后的`hadoop`是连接HDFS的用户名。这一段代码实现了与HDFS的连接。 接下来,就可以用`FSDataInputStream`类来读取HDFS上的文件,如下所示: ```java Path filePath = new Path("/path/to/file"); FSDataInputStream inStream = fs.open(filePath); ``` 这,`Path`是Hadoop提供的一个表示HDFS文件路径的类,其中的参数是需要读取文件在HDFS上的路径;`FSDataInputStream`是Hadoop提供的一个用于读取数据的数据流对象,`inStream`就是用于读取文件一个数据流实例对象。 读取文件之后,就可以把它转换成需要的数据格式。比如,可以用`BufferedReader`读取字符文件,如下所示: ```java BufferedReader reader = new BufferedReader(new InputStreamReader(inStream)); String line; while ((line = reader.readLine()) != null) { // 处理每一行数据 } ``` 在这,`BufferedReader`是Java提供的字符输入流缓存类,`InputStreamReader`是把字节输入流转换成字符输入流的类。使用`BufferedReader`可以一行一行地读取文件内容并进行处理。 除了字符文件之外,还可以读取二进制文件。比如,可以使用`ByteArrayOutputStream`将数据读取到字节数组中,如下所示: ```java ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outputStream.write(buffer, 0, len); } byte[] data = outputStream.toByteArray(); ``` 在这,`ByteArrayOutputStream`是Java提供的一个内存字节缓存区,使用`read`方法读取字节流,并将读取的字节数据写入缓存区中。最终使用`toByteArray`方法将缓存区中的数据转换成字节数组。 总之,使用Java API读取HDFS数据能够方便地实现对Hadoop集群中的大量数据的处理,给大数据领域的开发和运维带来了极大的便捷性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值