软件构造Lab_5 读取大文本文件的处理与优化

这篇博客探讨了在软件构造实验中,如何优化读取大文本文件并构建椭圆轨道系统的过程。通过使用Visual VM工具,作者发现原本的add和set方法存在O(n^2)的时间复杂度,经过优化后降低到了O(n)。此外,还重构了计算逻辑距离的代码,以适应大规模数据,显著提高了效率。


注:这篇博客是解决软件构造Lab_5中对人际关系轨道系统对于大文件处理速度太慢这个问题。

一、问题概述

这个实验的一部分任务就是读取一个大文本文件,之后建立椭圆轨道系统。这个大文本文件的大小差不多是90万行数据。原来提供构造轨道系统的最大文件差不多是10万行。(PS:这个文件我在上一个实验中使用未优化的代码测试了一下30分钟没有跑完)
所以任务就是将大文件的读取和建立轨道系统的时间保持在可以接受的时间范围之内。

二、开始优化

1. 工具

了解问题之后就要开始优化了,首先我们需要一个合适的工具。
在刚开始是不知道怎么方便的记录在哪里花费的时间长短,我先是使用了一个最容易想到的办法。增加输出语句进行观察,就像这样。

System.out.print(numberOfLine);   //查看文件读取行数
System.out.println(Duration.between(inst1, inst2).toMillis());  //使用inst1和inst2的时间差来查看花费的时间

首先这样有点蠢(虽然简单),其次这样不好用。在处理的过程中,难免遇到很多循环,如果将这样的语句放在循环内,将会在控制台打印出太多语句,难以观察,如果放在循环之外,有难以得知循环内部的信息,实在是下下之策。
所以,工欲善其事必先利其器。我们不使用这种方法,我们使用一个Visual VM的工具来帮我们寻找程序在那些位置执行花费了较长的时间。

2. 开始优化

在刚开始测试之前我一直以为是我的计算人们之间的逻辑距离是最耗费时间的其他没有太大的问题。事实上到最后优化完成我发现我的代码其实很多地方的时间复杂度相当的糟糕。
首先简单的介绍我原来的代码:1.将文件按行读入并进行处理。2.由读入的人际关系建立人际关系图。3.由人际关系图计算每个人的实际半径(逻辑距离)。4.将正确半径的人放入轨道系统中。

第一个优化点——时间复杂度

开始运行,发现好像只能完成第一步(???心里凉了一半,我还以为问题在第三步,谁知道第二步就解决不了)。使用Visual VM看看哪个地方问题最大。步骤2中使用的add(向图中增加顶点)函数和set函数(向图中增加边)都花费了不少的时间。但是我这两个函数在原来Lab_2的写的,这里只是复用原来的代码来完成这次实验的任务,没想到这两个函数是实际上问题那么大。在原来设计时,只是简单地将任务完成所以没有考虑时间复杂性这些问题,而且原来使用时根本没有那么大的数据量。
原来的add方法代码如下:

@Override
  public boolean add(S vertex) {  //原add代码,时间复杂度为O(sizeof(vertices))
    for (Vertex<S> oneVertex : vertices) {
      if (oneVertex.getLabel().equals(vertex)) { // 找到相同的vertex
        return false;
      }
    }
    vertices.add(new Vertex<S>(vertex)); // 未找到相同的vertex
    return true;
  }

新add方法:

@Override
  public boolean add(S vertex) {  //新add方法,时间复杂度为O(1)
    return vertices.add(new Vertex<S>(vertex));
  }

这个方法在每次处理一个人时都要调用一次,所以在第2部的时间复杂度应该是O(n^2)(n是图中人的个数),现在更改之后时间复杂度改为O(n)。
另外还有set方法,旧set方法如下:

@Override
  public int set(S source, S target, int weight) {   //旧set方法,时间复杂度相当糟糕O(sizeof(vertices)^2)
    boolean hasSource = false;
    boolean hasTarget = false;
    for (Vertex<S> oneVertex : vertices) { // 如果vertex中不存在source,将其加入
      if (oneVertex.getLabel().equals(source)) { // 找到相同的vertex
        hasSource = true;
        break;
      }
    }
    if (!hasSource) {
    if (!vertices.contains(new Vertex<S>(source))) {
      vertices.add(new Vertex<>(source));
    }
    for (Vertex<S> oneVertex : vertices) { // 如果vertex中不存在target,将其加入
      if (oneVertex.getLabel().equals(target)) { // 找到相同的vertex
        hasTarget = true;
        break;
      }
    }
    if (!hasTarget) {
    if (!vertices.contains(new Vertex<>(target))) {
      vertices.add(new Vertex<>(target));
    }
    for (Vertex<S> oneVertex : vertices) { // 遍历每一条现有边
      if (oneVertex.getLabel().equals(source) && oneVertex.getOutVertexes().containsKey(target)) {
        // 找到source,且source指向target,即那个边存在
        if (oneVertex.getOutVertexes().get(target) != 0) {
          int previousWeight = oneVertex.getOutVertexes().get(target);
          oneVertex.getOutVertexes().replace(target, weight);
          oneVertex.getInVertexes().replace(target, weight);
          return previousWeight;
        } else {
          oneVertex.getOutVertexes().replace(target, 0, weight);
          for (Vertex<S> otherVertex : vertices) {
            // 找到target,并将内容改变
            if (otherVertex.getLabel().equals(target)) {
              otherVertex.getInVertexes().replace(source, 0, weight);
            }
          }
          return 0;
        }
      }
    }
    // 如果那个边不存在
    for (Vertex<S> oneVertex : vertices) {
      // 找到target,并将内容改变
      if (oneVertex.getLabel().equals(source)) {
        oneVertex.getOutVertexes().put(target, weight);
      }
    }
    for (Vertex<S> oneVertex : vertices) {
      // 找到source,并将内容改变
      if (oneVertex.getLabel().equals(target)) {
        oneVertex.getInVertexes().put(source, weight);
      }
    }
    return 0;
  }

新的set方法:(除去无用的功能代码,精简功能并优化代码)

@Override
  public int set(S source, S target, int weight) { //新set方法时间复杂度O(sizeof(vertices))
    if (!vertices.contains(new Vertex<S>(source))) {
      vertices.add(new Vertex<>(source));
    }
    if (!vertices.contains(new Vertex<>(target))) {
      vertices.add(new Vertex<>(target));
    }
     // 如果那个边不存在
    for (Vertex<S> oneVertex : vertices) {
      // 找到target,并将内容改变
      if (oneVertex.getLabel().equals(source)) {
        oneVertex.getOutVertexes().put(target, weight);
      }
    }
    for (Vertex<S> oneVertex : vertices) {
      // 找到source,并将内容改变
      if (oneVertex.getLabel().equals(target)) {
        oneVertex.getInVertexes().put(source, weight);
      }
    }
    return 0;
  }

这个方法在每次处理每个关系时也要调用一次,所以和add方法一样,从n^2的复杂度降到n。
在经过这一番处理之后,步骤2的时间的确减了下来。

第二个优化点——重写代码

之后再测试,发现了意料之中的问题,步骤三很慢,特别是计算人际关系图中的逻辑距离时,很慢。
刚开始我怀疑过是不是我对象的hashCode方法有问题,所以使用了IDE自带的override hashCode的功能,但是发现这不是问题所在。之后我猜测是不是我的计算人际距离的方法复杂度太高,我尝试使用队列代替原来的数据结构,使用dijkstra算法求人际之间的逻辑距离,实际上时间复杂度没有优化和原来一样,这说明问题不是出在我这个方法的算法时间复杂性太高,而是这个方法不适合于我现在这个场景使用。
首先简要的介绍一下我原来的算法,其实和dijkstra算法类似,都是输入一个带权无向图,和两个顶点,输出两个顶点间的逻辑距离。但是现在我的需求不是这样随便输入两个定点(两个人),而是其中一个人一直输入,另一个定点的则遍历其余的所有人,计算他们之间的距离。所以传统的dijkstra算法在这里就显得不合适了,所以时间花费异常高。
最后在多次想尝试修改原来的方法失败之后,决定重写新的方法。其实新的方法说起来相当简单。

  1. 将距离中心点直接邻接的人找到,加入队列中,并将他们的距离记为1.
  2. 遍历现在的队列将与现在队列里元素直接邻接且没有遍历过的加入新的队列中距离为原来的距离加一。
  3. 如果新队列里还有元素就更新队列,进行步骤2。
  4. 如果新队列里没有元素就结束计算。

这样只需要遍历一遍所有的顶点就可以求出所需要的所有距离,时间大大缩短。

其余的修改只是小修小补,与这两者大致相同只是对时间优化的程度没那么强,所以不再赘述。

三、结束

老师在上课时说优化代码大部分只是雕虫小技。但是轮到我自己在优化代码时,还是发现了自己写代码时的不少问题。首先设计方法时,没有注意自己设计的方法原来那么烂(许多没有必要的操作,白白增加时间复杂性),其实在使用复用原来的代码时没有搞清楚适用情况,导致代码在原来的情况下使用,在新情况下不再合适。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值