浅谈Java程序优化

颜色说明

颜色

说明

*****橙色*****

重点优化对象,必须遵守。

*****黑色*****

能够提高性能,建议使用,但是不强制要求。

一、      避免在循环条件中使用复杂表达式

在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。

例子:

import java.util.Vector;

 

class CEL

{

    void method(Vector vector)

    {

        for (int i = 0; i < vector.size(); i++) // Violation

        ; // ...

    }

}

 

更正:

class CEL_fixed

{

void method (Vectorvector)

{

        int size = vector.size();

        for (int i = 0; i < size; i++)      ; // ...

    }

}

二、     为'Vectors' 和 'Hashtables'定义初始大小

JVM为Vector扩充大小的时候需要重新创建一个更大的数组,将原原先数组中的内容复制过来,最后,原先的数组再被回收。可见Vector容量的扩大是一个颇费时间的事。通常,默认的10个元素大小是不够的。你最好能准确的估计你所需要的最佳大小。

例子:

import java.util.Vector;

 

public class DIC

{

    public void addObjects(Object[] o)

    {

        // if length > 10, Vector needs to expand

        for (int i = 0; i < o.length; i++)

        {

            v.add(o); // capacity before it can add more elements.

        }

    }

 

    public Vector v = new Vector(); // no initialCapacity.

}

更正:

自己设定初始大小。

    public Vector v = new Vector(20);

public Hashtable hash = new Hashtable(10);

三、     finally块中关闭数据库连接、I/O流操作,以释放资源

程序中使用到的资源应当被释放,以避免资源泄漏。这最好在finally块中去做。不管程序执行的结果如何,finally块总是会执行的,以确保资源的正确关闭。

例子:

import java.io.*;

public class CS

{

    public static void main(String args[])

    {

        CS cs = new CS();

        cs.method();

    }

   

    public void method()

{

        FileInputStream fis = null;

        try

        {

            fis = new FileInputStream("CS.java");

            int count = 0;

            while (fis.read() != -1)

            {

                count++;

            }

            System.out.println(count);

        }

        catch (FileNotFoundException e1)

        {

        }

        catch (IOException e2)

        {

        }

        finally

       {

           fis.close();

       }

    }

}

四、     使用'System.arraycopy ()'代替通过来循环复制数组

'System.arraycopy ()' 要比通过循环来复制数组快的多。

例子:

public class IRB

{

    void method()

    {

        int[] array1 = new int[100];

        for (int i = 0; i < array1.length; i++)

        {

            array1 = i;

        }

        int[] array2 = new int[100];

        for (int i = 0; i < array2.length; i++)

        {

            array2 = array1; // Violation

        }

    }

}

更正:

public class IRB

{

    void method()

    {

        int[] array1 = new int[100];

        for (int i = 0; i < array1.length; i++)

        {

            array1 = i;

        }

        int[] array2 = new int[100];

        System.arraycopy(array1, 0, array2, 0,100);

    }

}

五、     让访问实例内变量的getter/setter方法变成”final”

简单的getter/setter方法应该被置成final,这会告诉编译器,这个方法不会被重载,所以,可以变成”inlined”

例子:

class MAF {

    public void setSize (int size) {

         _size = size;

    }

    private int _size;

}

 

更正:

class DAF_fixed {

    final public void setSize (int size) {

         _size = size;

    }

    private int _size;

}

六、     避免不需要的instanceof操作

如果左边的对象的静态类型等于右边的,instanceof表达式返回永远为true。

例子:

public class UISO {

    public UISO () {}

}

class Dog extends UISO {

    void method (Dog dog, UISO u) {

        Dog d = dog;

        if (d instanceof UISO) // always true.

            System.out.println("Dog is aUISO");

        UISO uiso = u;

        if (uiso instanceof Object) // alwaystrue.

            System.out.println("uiso is anObject");

    }

}

更正:

删掉不需要的instanceof操作。

class Dog extends UISO {

    void method () {

        Dog d;

        System.out.println ("Dog is anUISO");

        System.out.println ("UISO is anUISO");

    }

}

七、     避免不需要的造型操作

所有的类都是直接或者间接继承自Object。同样,所有的子类也都隐含的"等于"其父类。那么,由子类造型至父类的操作就是不必要的了。

例子:

class UNC

{

    String _id = "UNC";

}

 

class Dog extends UNC

{

    void method()

    {

        Dog dog = new Dog();

        UNC animal = (UNC)dog; // not necessary.

        Object o = (Object)dog; // not necessary.

    }

}

更正:

class Dog extends UNC

{

    void method()

    {

        Dog dog = new Dog();

        UNC animal = dog;

        Object o = dog;

    }

}

八、     如果只是查找单个字符的话,用charAt()代替startsWith()

用一个字符作为参数调用startsWith()也会工作的很好,但从性能角度上来看,调用用String API无疑是错误的!

例子:

public class PCTS

{

    private void method(String s)

    {

        if (s.startsWith("a"))

        { // violation

            // ...

        }

    }

}

更正

将'startsWith()' 替换成'charAt()'.

public class PCTS

{

    private void method(String s)

    {

        if ('a' == s.charAt(0))

        {

            // ...

        }

    }

}

九、     使用移位操作来代替'a / b'操作

"/"是一个很"昂贵"的操作,使用移位操作将会更快更有效。

例子:

public class SDIV

{

    public static final int NUM = 16;

 

    public void calculate(int a)

    {

        int div = a / 4; // should be replaced with "a >> 2".

        int div2 = a / 8; // should be replaced with "a >> 3".

        int temp = a / 3;

   }

}

更正:

public class SDIV

{

    public static final int NUM = 16;

 

    public void calculate(int a)

    {

        int div = a >> 2;

        int div2 = a >> 3;

        int temp = a / 3; // 不能转换成位移操作

    }

}

十、     使用移位操作代替'a * b'

同上。

但我个人认为,除非是在一个非常大的循环内,性能非常重要,而且你很清楚你自己在做什么,方可使用这种方法。否则提高性能所带来的程序晚读性的降低将是不合算的。

例子:

public class SMUL

{

    public void calculate(int a)

    {

        int mul = a * 4; // should be replaced with "a << 2".

        int mul2 = 8 * a; // should be replaced with "a << 3".

        int temp = a * 3;

    }

}

更正:

package OPT;

 

public class SMUL

{

    public void calculate(int a)

    {

        int mul = a << 2;

        int mul2 = a << 3;

        int temp = a * 3; // 不能转换

    }

}

十一、        在字符串相加的时候,使用 ' ' 代替 "",如果该字符串只有一个字符的话

例子:

public class STR {

    public void method(String s) {

        String string = s + "d"  // violation.

        string = "abc" +"d"      // violation.

    }

}

更正:

将一个字符的字符串替换成' '

public class STR {

    public void method(String s) {

        String string = s + 'd'

        string = "abc" + 'd'  

    }

}

十二、        不要在循环中调用synchronized(同步)方法

方法的同步需要消耗相当大的资料,在一个循环中调用它绝对不是一个好主意。

例子:

import java.util.Vector;

 

public class SYN

{

    public synchronized void method(Object o)

    {

    }

 

    private void test()

    {

        for (int i = 0; i < vector.size(); i++)

        {

            method(vector.elementAt(i)); // violation

        }

    }

 

    private Vector vector = new Vector(5, 5);

}

 

更正:

不要在循环体中调用同步方法,如果必须同步的话,推荐以下方式:

import java.util.Vector;

 

public class SYN

{

    public void method(Object o)

    {

    }

 

    private void test ()

    {

        synchronized

        {

            //在一个同步块中执行非同步方法

            for (int i = 0; i <vector.size(); i++)

            {

                method(vector.elementAt(i));  

            }

        }

    }

 

    private Vector vector = new Vector(5, 5);

}

 

十三、        try/catch块移出循环

把try/catch块放入循环体内,会极大的影响性能,如果编译JIT被关闭或者你所使用的是一个不带JIT的JVM,性能会将下降21%之多!

例子:

import java.io.FileInputStream;

 

public class TRY

{

    void method(FileInputStream fis)

    {

        for (int i = 0; i < size; i++)

        {

            try

            {

                // violation

                _sum += fis.read();

            }

            catch (Exception e)

            {

            }

        }

    }

 

    private int _sum;

}

更正:

将try/catch块移出循环

    void method (FileInputStream fis)

    {

        try

        {

            for (int i = 0; i <size; i++)

            {

                _sum += fis.read();

            }

        }

        catch (Exception e)

        {

           

        }

    }

十四、        对于boolean值,避免不必要的等式判断

将一个boolean值与一个true比较是一个恒等操作(直接返回该boolean变量的值).移走对于boolean的不必要操作至少会带来2个好处:

1)代码执行的更快 (生成的字节码少了5个字节);

2)代码也会更加干净 。

例子:

public class UEQ

{

booleanmethod (String string)

{

        return string.endsWith ("a")== true;   // Violation

    }

}

 

更正:

class UEQ_fixed

{

booleanmethod (String string)

{

        return string.endsWith("a");

    }

}

十五、        对于常量字符串,用'String' 代替 'StringBuffer'

常量字符串并不需要动态改变长度。

例子:

public class USC

{

Stringmethod ()

{

        StringBuffer s = new StringBuffer("Hello");

        String t = s + "World!";

        return t;

    }

}

更正:

把StringBuffer换成String,如果确定这个String不会再变的话,这将会减少运行开销提高性能。

 

十六、        'StringTokenizer' 代替'indexOf()' 'substring()'

字符串的分析在很多应用中都是常见的。使用indexOf()和substring()来分析字符串容易导致StringIndexOutOfBoundsException。而使用StringTokenizer类来分析字符串则会容易一些,效率也会高一些。

例子:

public class UST

{

    void parseString(String string)

    {

        int index = 0;

        while ((index = string.indexOf(".", index)) !=-1)

        {

            System.out.println(string.substring(index,string.length()));

        }

    }

}

十七、        不要在循环体中实例化变量

在循环体中实例化临时变量将会增加内存消耗

 

例子:        

import java.util.Vector;

 

public class LOOP

{

    void method(Vector v)

    {

        for (int i = 0; i < v.size(); i++)

        {

            Object o = new Object();

            o = v.elementAt(i);

        }

    }

}

更正:

在循环体外定义变量,并反复使用

import java.util.Vector;

 

public class LOOP

{

    void method(Vector v)

    {

        Object o;

        for (int i = 0; i < v.size(); i++)

        {

            o = v.elementAt(i);

        }

    }

}

十八、        确定 StringBuffer的容量

StringBuffer的构造器会创建一个默认大小(通常是16)的字符数组。在使用中,如果超出这个大小,就会重新分配内存,创建一个更大的数组,并将原先的数组复制过来,再丢弃旧的数组。在大多数情况下,你可以在创建StringBuffer的时候指定大小,这样就避免了在容量不够的时候自动增长,以提高性能。

例子:

public class RSBC

{

    void method()

    {

        StringBuffer buffer = new StringBuffer(); // violation

        buffer.append("hello");

    }

}

更正:

为StringBuffer提供大小。

public class RSBC

{

    void method()

    {

        StringBuffer buffer = new StringBuffer(MAX);

        buffer.append("hello");

    }

 

    private final int MAX = 100;

}

十九、        尽可能的使用栈变量

如果一个变量需要经常访问,那么你就需要考虑这个变量的作用域了。static? local?还是实例变量?访问静态变量和实例变量将会比访问局部变量多耗费2-3个时钟周期。

例子:

public class USV

{

    void getSum(int[] values)

    {

        for (int i = 0; i < value.length; i++)

        {

            _sum += value; // violation.

        }

    }

 

    void getSum2(int[] values)

    {

        for (int i = 0; i < value.length; i++)

        {

            _staticSum += value;

        }

    }

 

    private int _sum;

    private static int _staticSum;

}

更正:

如果可能,请使用局部变量作为你经常访问的变量。

你可以按下面的方法来修改getSum()方法:

void getSum (int[] values)

{

    int sum = _sum; // temporary local variable.

    for (int i=0; i < value.length; i++)

    {

        sum += value;

    }

    _sum = sum;

}

二十、        不要总是使用取反操作符(!)

取反操作符(!)降低程序的可读性,所以不要总是使用。

例子:

public class test

{

    boolean method(boolean a, boolean b)

    {

        if (!a)

        {

            return !a;

        }

        else

        {

            return !b;

        }

    }

}

更正:

如果可能不要使用取反操作符(!)

二十一、           与一个接口进行instanceof操作

基于接口的设计通常是件好事,因为它允许有不同的实现,而又保持灵活。只要可能,对一个对象进行instanceof操作,以判断它是否某一接口要比是否某一个类要快。

例子:

public class INSOF {

    private void method (Object o) {

        if (o instanceof InterfaceBase) {}  // better

        if (o instanceof ClassBase) { }   // worse.

    }

}

 

class ClassBase {}

interface InterfaceBase {}

二十二、           异常对性能不利

抛出异常首先要创建一个新的对象。Throwable接口的构造函数调用名为fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,VM就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。

异常只能用于错误处理,不应该用来控制程序流程。

 

二十三、           不用new关键词创建类的实例

用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用。但如果一个对象实现了Cloneable接口,我们可以调用它的clone()方法。clone()方法不会调用任何类构造函数。

在使用设计模式(Design Pattern)的场合,如果用Factory模式创建对象,则改用clone()方法创建新的对象实例非常简单。例如,下面是Factory模式的一个典型实现:

public static Credit getNewCredit()

{

    return new Credit();

}

改进后的代码使用clone()方法,如下所示:

private static Credit BaseCredit =new Credit();

public static Credit getNewCredit() {

return (Credit) BaseCredit.clone();

}

二十四、           尽量使用局部变量

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

二十五、           避免对象创建和GC

只要有可能,应该避免创建对象,防止调用构造函数带来的相关性能成本,以及在对象结束其生命周期时进行垃圾收集所带来的成本。考虑以下这些准则:

·    只要有可能,就使用基本变量类型,而不使用对象类型。例如,使用 int,而不使用 Integer

·    缓存那些频繁使用的寿命短的对象,避免一遍又一遍地重复创建相同的对象,并因此加重垃圾收集的负担;

·    在处理字符串时,使用 StringBuffer 而不使用字符串String进行连接操作,因为字符串对象具有不可变的特性,并且需要创建额外的字符串对象以完成相应的操作,而这些对象最终必须经历 GC

·    避免过度地进行 Java 控制台的写操作,降低字符串对象处理、文本格式化和输出带来的成本;

·    实现数据库连接池,重用连接对象,而不是重复地打开和关闭连接

·    使用线程池(thread pooling),避免不停地创建和删除线程对象,特别是在大量使用线程的时候;

·    避免在代码中调用GCGC是一个停止所有处理(stop the world的事件,它意味着除了 GC 线程自身外,其他所有执行线程都将处于挂起状态。如果必须调用 GC,那么可以在非紧急阶段或空闲阶段实现它;

·    避免在循环内分配对象。

·    尽早释放无用对象的引用。大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域(scope)后,自动设置为null。我们在使用这种方式时候,必须特别注意一些复杂的对象,例如数组,队列,树,图等,这些对象之间的相互引用关系较为复杂。对于这类对象,GC回收它们一般效率较低。如果程序允许,尽早将不再使用的引用对象赋为null。这样可以加速GC的工作。

·    如果有经常使用的图片,可以使用soft引用类型。它可以尽可能将图片保存在内存中,供程序调用,而不引起Out Of Memory

·    注意一些全局的变量,以及一些静态变量。这些变量往往容易引起悬挂对象(dangling reference),造成内存浪费。

·    使用String a ="a";定义字符串,而不是使用Stringa = new String("a");

二十六、           避免非常大的分配

有时候问题不是由当时的堆状态造成的,而是因为分配失败造成的。分配的内存块都必须是连续的,而随着堆越来越满,找到较大的连续块越来越困难。这不仅仅是 Java 的问题,使用 C 中的 malloc 也会遇到这个问题。JVM 在压缩阶段通过重新分配引用来减少碎片,但其代价是要冻结应用程序较长的时间。

 

二十七、           SQL语句大写

在JAVA + ORACLE 的应用系统开发中,java中内嵌的SQL语句尽量使用大写的形式,以减轻ORACLE解析器的解析负担。

 

二十八、           尽量重用对象,特别是String 对象的使用中,出现字符串连接情况时应用StringBuffer代替

由于系统不仅要花时间生成对象,以后可能还需花时间对这些对象进行垃圾回收和处理。因此,生成过多的对象将会给程序的性能带来很大的影响;

例子:

StringBuffer sqlbuff = newStringBuffer();

 

sqlbuff.append("select to_char(decode(parent_item_id ,(select parent_item_id froms_finance_item_2009 where item_id=10),null,parent_item_id)) up_id, "

+

" to_char(item_id)id,item_name name ");

 

sqlbuff.append(" froms_finance_item_2009 start with item_id=10 connect by parent_item_id= prioritem_id ");

 

二十九、           由于JVM的有其自身的GC机制,不需要程序开发者的过多考虑,从一定程度上减轻了开发者负担,但同时也遗漏了隐患,过分的创建对象会消耗系统的大量内存,严重时会导致内存泄露;

JVM回收垃圾的条件是:对象不在被引用;然而,JVM的GC并非十分的机智,即使对象满足了垃圾回收的条件也不一定会被立即回收。所以,建议我们在对象使用完毕,应手动置成null;

 

示例代码:

Object[] rvo = new Object[2];

ArrayList tlist = new ArrayList();

tlist = BOQueryResult.getInstence().getQueryResult(sql,BOQueryResult.integrateSqlAgent);

if (tlist.size() == 1)

{

    rvo = (Object[]) tlist.get(0);

    //对象使用完毕,手工释放对象

    tlist = null;

}

return rvo;

 

三十、        尽量使用HashMap ArrayList ,除非必要,否则不推荐使用HashTableVector ,后者由于使用同步机制,而导致了性能的开销;

 

三十一、           array(数组) ArryList的使用

array([]):最高效;但是其容量固定且无法动态改变;
ArrayList:容量可动态增长;但牺牲效率;
基于效率和类型检验,应尽可能使用array,无法确定数组大小时才使用ArrayList!
ArrayList是Array的复杂版本
ArrayList内部封装了一个Object类型的数组,从一般的意义来说,它和数组没有本质的差别,甚至于ArrayList的许多方法,如Index、IndexOf、Contains、Sort等都是在内部数组的基础上直接调用Array的对应方法。
ArrayList存入对象时,抛弃类型信息,所有对象屏蔽为Object,编译时不检查类型,但是运行时会报错。
注:jdk5中加入了对泛型的支持,已经可以在使用ArrayList时进行类型检查。
从这一点上看来,ArrayList与数组的区别主要就是由于动态增容的效率问题了;

 

三十二、           在进行金额计算时使用BigDecimal

对于帐务系统中金额处理的运行,一律采用BigDecimal。

 

转换元为分

        BigDecimal money = new BigDecimal(100);

       money = money.movePointLeft(2);

处理结果为:1.00

转换分为元

        BigDecimal money = new BigDecimal(100);

       money = money.movePointRight(2);

处理结果为:10000

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值