欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

通过Java代码技巧改善性能

程序员文章站 2023-10-23 00:02:51
前言 程序的性能受到代码质量的直接影响。这次主要介绍一些代码编写的小技巧和惯例。虽然看起来有些是微不足道的编程技巧,却可能为系统性能带来成倍的提升,因此还是值得关注的...

前言

程序的性能受到代码质量的直接影响。这次主要介绍一些代码编写的小技巧和惯例。虽然看起来有些是微不足道的编程技巧,却可能为系统性能带来成倍的提升,因此还是值得关注的。

慎用异常

在java开发中,经常使用try-catch进行错误捕获,但是try-catch语句对系统性能而言是非常糟糕的。虽然一次try-catch中,无法察觉到她对性能带来的损失,但是一旦try-catch语句被应用于循环或是遍历体内,就会给系统性能带来极大的伤害。

以下是一段将try-catch应用于循环体内的示例代码:

@test
  public void test11() {

    long start = system.currenttimemillis();
    int a = 0;
    for(int i=0;i<1000000000;i++){
      try {
        a++;
      }catch (exception e){
        e.printstacktrace();
      }
    }
    long usetime = system.currenttimemillis()-start;
    system.out.println("usetime:"+usetime);

  }

上面这段代码运行结果是:

usetime:10

下面是一段将try-catch移到循环体外的代码,那么性能就提升了将近一半。如下:

@test
  public void test(){
    long start = system.currenttimemillis();
    int a = 0;
    try {
      for (int i=0;i<1000000000;i++){
        a++;
      }
    }catch (exception e){
      e.printstacktrace();
    }
    long usetime = system.currenttimemillis()-start;
    system.out.println(usetime);
  }

运行结果:

usetime:6

使用局部变量

调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(stack)中,速度快。其他变量,如静态变量、实例变量等,都在堆(heap)中创建,速度较慢。

下面是一段使用局部变量进行计算的代码:

@test
  public void test11() {

    long start = system.currenttimemillis();
    int a = 0;
    for(int i=0;i<1000000000;i++){
      a++;
    }
    long usetime = system.currenttimemillis()-start;
    system.out.println("usetime:"+usetime);

  }

运行结果:

usetime:5

将局部变量替换为类的静态变量:

static int aa = 0;
  @test
  public void test(){
    long start = system.currenttimemillis();

    for (int i=0;i<1000000000;i++){
      aa++;
    }
    long usetime = system.currenttimemillis()-start;
    system.out.println("usetime:"+usetime);
  }

运行结果:

usetime:94

通过上面两次的运行结果,可以看出来局部变量的访问速度远远高于类成员变量。

位运算代替乘除法

在所有的运算中,位运算是最为高效的。因此,可以尝试使用位运算代替部分算术运算,来提高系统的运行速度。最典型的就是对于整数的乘除运算优化。

下面是一段使用算术运算的代码:

@test
  public void test11() {

    long start = system.currenttimemillis();
    int a = 0;
    for(int i=0;i<1000000000;i++){
      a*=2;
      a/=2;
    }
    long usetime = system.currenttimemillis()-start;
    system.out.println("usetime:"+usetime);
  }

运行结果:

usetime:1451

将循环体中的乘除运算改为等价的位运算,代码如下:

@test
  public void test(){
    long start = system.currenttimemillis();
    int aa = 0;
    for (int i=0;i<1000000000;i++){
      aa<<=1;
      aa>>=1;
    }
    long usetime = system.currenttimemillis()-start;
    system.out.println("usetime:"+usetime);
  }

运行结果:

usetime:10

上两段代码执行了完全相同的功能,在每次循环中,都将整数乘以2,并除以2。但是运行结果耗时相差非常大,所以位运算的效率还是显而易见的。

提取表达式

在软件开发过程中,程序员很容易有意无意地让代码做一些“重复劳动”,在大部分情况下,由于计算机的高速运行,这些“重复劳动”并不会对性能构成太大的威胁,但若希望将系统性能发挥到极致,提取这些“重复劳动”相当有意义。

比如以下代码中进行了两次算术计算:

@test
  public void testexpression(){
    long start = system.currenttimemillis();
    double d = math.random();
    double a = math.random();
    double b = math.random();
    double e = math.random();

    double x,y;
    for(int i=0;i<10000000;i++){
      x = d*a*b/3*4*a;
      y = e*a*b/3*4*a;
    }
    long usetime = system.currenttimemillis()-start;
    system.out.println("usetime:"+usetime);

  }

运行结果:

usetime:21

仔细看能发现,两个计算表达式的后半部分完全相同,这也意味着在每次循环中,相同部分的表达式被重新计算了。

那么改进一下后就变成了下面的样子:

@test
  public void testexpression99(){
    long start = system.currenttimemillis();
    double d = math.random();
    double a = math.random();
    double b = math.random();
    double e = math.random();

    double p,x,y;
    for(int i=0;i<10000000;i++){
      p = a*b/3*4*a;
      x = d*p;
      y = e*p;
    }
    long usetime = system.currenttimemillis()-start;
    system.out.println("usetime:"+usetime);
  }

运行结果:

usetime:11

通过运行结果我们可以看出来具体的优化效果。

同理,如果在某循环中需要执行一个耗时操作,而在循环体内,其执行结果总是唯一的,也应该提取到循环体外。

例如下面的代码:

for(int i=0;i<100000;i++){
  x[i] = math.pi*math.sin(y)*i;
}

应该改进成下面的代码:

//提取复杂,固定结果的业务逻辑处理到循环体外
double p = math.pi*math.sin(y);
for(int i=0;i<100000;i++){
  x[i] = p*i;
}

使用arraycopy()

数组复制是一项使用频率很高的功能,jdk中提供了一个高效的api来实现它。

/**
   * @param   src   the source array.
   * @param   srcpos  starting position in the source array.
   * @param   dest   the destination array.
   * @param   destpos starting position in the destination data.
   * @param   length  the number of array elements to be copied.
   * @exception indexoutofboundsexception if copying would cause
   *        access of data outside array bounds.
   * @exception arraystoreexception if an element in the <code>src</code>
   *        array could not be stored into the <code>dest</code> array
   *        because of a type mismatch.
   * @exception nullpointerexception if either <code>src</code> or
   *        <code>dest</code> is <code>null</code>.
   */
  public static native void arraycopy(object src, int srcpos,
                    object dest, int destpos,
                    int length);

如果在应用程序中需要进行数组复制,应该使用这个函数,而不是自己实现。

下面来举例:

@test
  public void testarraycopy(){
    int size = 100000;
    int[] array = new int[size];
    int[] arraydest = new int[size];

    for(int i=0;i<array.length;i++){
      array[i] = i;
    }
    long start = system.currenttimemillis();
    for (int k=0;k<1000;k++){
      //进行复制
      system.arraycopy(array,0,arraydest,0,size);
    }
    long usetime = system.currenttimemillis()-start;
    system.out.println("usetime:"+usetime);
  }

运行结果:

usetime:59

相对应地,如果在程序中,自己实现数组复制,其等价代码如下:

@test
  public void testarraycopy99(){
    int size = 100000;
    int[] array = new int[size];
    int[] arraydest = new int[size];

    for(int i=0;i<array.length;i++){
      array[i] = i;
    }
    long start = system.currenttimemillis();
    for (int k=0;k<1000;k++){
      for(int i=0;i<size;i++){
        arraydest[i] = array[i];
      }
    }
    long usetime = system.currenttimemillis()-start;
    system.out.println("usetime:"+usetime);
  }

运行结果:

usetime:102

通过运行结果可以看出效果。

因为system.arraycopy()函数是native函数,通常native函数的性能要优于普通函数。仅出于性能考虑,在程序开发时,应尽可能调用native函数。