Java性能优化一则

背景:

最近在做Provider Search项目,其中有一个环节就是DBA有一个脚本会定期把一些特定的数据从测试环境逐级推到PROD。这就要求我们有一个测试脚本来测试DBA的这个数据脚本是否正确。

早先时候,因为所有的数据都是存在Oracle中,所以测试起来比较方便,因为两台Oracle之间可以创建一个连接,这样我们只要运行一个SQL中间用minus来把两边查出来的数据相减就知道数据是否有差异。但是现在测试环境中我们把数据迁移到了MySQL,而DBA暂时还没时间去研究Oracle到MySQL之间的数据连接。所以我们QA的测试脚本需要重新实现。

实现方法:

如果按照最简单的方法,我们只要查询两组数据的记录数是否一致就可以了,但如果有数据差异的情况下,DBA就不太好调查问题。所以我们就在Oracle和MySQL上分别运行同一条SQL取出一组数据,以DataSet方式返回。然后把这些数据放入HashMap中。再遍历HashMap把相同的数据删除掉,到最后就剩下那些不同的数据,我们就可以让DBA去调查数据不一致的原因。

问题浮现:

以前在两台Oracle之间进行数据比较的速度是非常快的,即使结果集有一百万条记录也可以在几分钟之内就比较完返回结果。但现在把数据取到客户机内存却存在严重的性能问题。把数据载入到DataSet就花费了非常多的时间。测试了一下一百万条记录的数据集,需要半个多小时才能完成载入到内存。而从Oracle载入数据到内存比从MySQL载入要慢的多。

以这样的速度来比较所有的数据估计得用上大半天时间,明显是不高效的。跟原先的程序速度比较起来相关十万八千里。捉摸着估计是代码里有臭虫。

开始调查:

虽然已经是晚上10:30,不过这个挑战实在有点提神。于是我打开代码开始查看。

有一种灵感驱使着我直接打开从数据库读取数据到DataSet的那些代码。看了几眼代码就非常兴奋,就像管道工人发现管道堵塞的原因一样兴奋。请看以下代码:

 public DataRow[] Rows = new DataRow[0];
public void AddRow(DataRow row)
    {
        DataRow[] _rows = Rows.clone();
        this.Rows = null;
        this.Rows = new DataRow[_rows.length+1];
        for(int i=0;i<_rows.length;i++)
        {
            this.Rows[i] = _rows[i];
        }
        this.Rows[this.Rows.length-1] = row;
        _rows = null;
    }
    
   

看到没有,恶心不?每当向DataSet添加一行的时候都要重新创建一个数组来装进这个新的元素。DataSet的相关代码是我很早以前从网上复制过来的,没经过审核就直接用上去,没想到里面竟然有如此耗性能的代码。当数据集里有N条记录的时候,这个算法的复杂度就是O(1+2+3+...+N)。

问题解决:

思索了两分钟,决定用List替代数组来装数据,于是就有了以下代码:

List<DataColumn> dataColumns = new ArrayList<DataColumn>();
 public void addColumn(DataColumn col)
    {
        dataColumns.add(col);
    }

后来经过测试,从一条SQL原先从MySQL载入数据到DataSet需要10分钟,现在只要几秒种。性能提高了非常多。

结论:

1. 用clone()的方法会消耗掉一些内存,当数据量大的时候,会很快把内存消耗掉,从而导致GC频繁操作,从而占用比较多的CPU。

2. 当数据库结构里的元素数量有频繁扩展的需要的时候,不要选用数组。因为这样就需要我们去新创建一个更大的数组。而数组在内存里必须是连续的空间,所以开辟一个新的数组空间是比较耗费资源的。这种情况下,我们应该选取链表结构来存储数据。

本例后面使用的ArrayList其实也是数组结构,但是当它在扩展空间的时候会根据一个算法来自动新增N个空元素,以达到提高效率的目的。

public void ensureCapacity(int minCapacity) {
    modCount++;
    int oldCapacity = elementData.length;
    if (minCapacity > oldCapacity) {
        Object oldData[] = elementData;
        int newCapacity = (oldCapacity * 3)/2 + 1;
            if (newCapacity < minCapacity)
        newCapacity = minCapacity;
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
    }
    }

 --Kenny

posted @ 2012-09-04 13:15  eHealth QA  阅读(1425)  评论(2编辑  收藏  举报