Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 384|回复: 0

[Java基础知识]Java程序性能优化

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-10-1 15:01:59 | 显示全部楼层 |阅读模式
    一、避免在循环条件中使用复杂表达式       在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。

    例子:
    import java.util.Vector;
    class CEL {
         void method (Vector vector) {
             for (int i = 0; i < vector.size (); i++)  // Violation
                 ; // ...
         }
    }
    复制代码

      


    更正:
    class CEL_fixed {
         void method (Vector vector) {
             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);

    参考资料:
    Dov Bulka, "Java Performance and Scalability Volume 1: Server-Side Programming
    Techniques" Addison Wesley, ISBN: 0-201-70429-3 pp.55 ?C 57

    三、在finally块中关闭Stream
    程序中使用到的资源应当被释放,以避免资源泄漏。这最好在finally块中去做。不管程序执行的结果如何,finally块总是会执行的,以确保资源的正确关闭。
             
    例子:
    import java.io.*;
    public class CS {
         public static void main (String args[]) {
             CS cs = new CS ();
             cs.method ();
         }
         public void method () {
             try {
                 FileInputStream fis = new FileInputStream ("CS.java");
                 int count = 0;
                 while (fis.read () != -1)
                     count++;
                 System.out.println (count);
                 fis.close ();
             } catch (FileNotFoundException e1) {
             } catch (IOException e2) {
             }
         }
    }
             
    更正:
    在最后一个catch后添加一个finally块

    参考资料:
    Peter Haggar: "Practical Java - Programming Language Guide".
    Addison Wesley, 2000, pp.77-79
    四、使用"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);
         }
    }
             
    参考资料:
    http://www.cs.cmu.edu/~jch/java/speed.HTML

    五、让访问实例变量的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;
    }

    参考资料:
    Warren N. and Bishop P. (1999), "Java in Practice", p. 4-5
    Addison-Wesley, ISBN 0-201-36065-9

    六、避免不需要的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 a UISO");
             UISO uiso = u;
             if (uiso instanceof Object) // always true.
                 System.out.println("uiso is an Object");
         }
    }
             
    更正:         
    删掉不需要的instanceof操作。
             
    class Dog extends UISO {
         void method () {
             Dog d;
             System.out.println ("Dog is an UISO");
             System.out.println ("UISO is an UISO");
         }
    }

    七、避免不需要的造型操作
    所有的类都是直接或者间接继承自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;
         }
    }
             
    参考资料:
    Nigel Warren, Philip Bishop: "Java in Practice - Design Styles and Idioms
    for Effective Java".  Addison-Wesley, 1999. pp.22-23
    八、如果只是查找单个字符的话,用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)) {
                 // ...
             }
         }
    }
             
    参考资料:
    Dov Bulka, "Java Performance and Scalability Volume 1: Server-Side Programming
    Techniques"  Addison Wesley, ISBN: 0-201-70429-3
    九、使用移位操作来代替"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) {}
         }
             
    参考资料:
    Peter Haggar: "Practical Java - Programming Language Guide".
    Addison Wesley, 2000, pp.81 ?C 83

    十四、对于boolean值,避免不必要的等式判断
    将一个boolean值与一个true比较是一个恒等操作(直接返回该boolean变量的值). 移走对于boolean的不必要操作至少会带来2个好处:
    1)代码执行的更快 (生成的字节码少了5个字节);
    2)代码也会更加干净 。

    例子:
    public class UEQ
    {
         boolean method (String string) {
             return string.endsWith ("a") == true;   // Violation
         }
    }

    更正:
    class UEQ_fixed
    {
         boolean method (String string) {
             return string.endsWith ("a");
         }
    }

    十五、对于常量字符串,用"String" 代替 "StringBuffer"  
    常量字符串并不需要动态改变长度。
    例子:
    public class USC {
         String method () {
             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()));
             }
         }
    }

    参考资料:
    Graig Larman, Rhett Guthrie: "Java 2 Performance and Idiom Guide"
    Prentice Hall PTR, ISBN: 0-13-014260-3 pp. 282 ?C 283

    十七、使用条件操作符替代"if (cond) return; else return;" 结构
    条件操作符更加的简捷
    例子:
    public class IF {
         public int method(boolean isDone) {
             if (isDone) {
                 return 0;
             } else {
                 return 10;
             }
         }
    }

    更正:
    public class IF {
         public int method(boolean isDone) {
             return (isDone ? 0 : 10);
         }
    }
    十八、使用条件操作符代替"if (cond) a = b; else a = c;" 结构
    例子:
    public class IFAS {
         void method(boolean isTrue) {
             if (isTrue) {
                 _value = 0;
             } else {
                 _value = 1;
             }
         }
         private int _value = 0;
    }

    更正:
    public class IFAS {
         void method(boolean isTrue) {
             _value = (isTrue ? 0 : 1);       // compact expression.
         }
         private int _value = 0;
    }

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

    例子:         
    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;
    }
             
    参考资料:
    Dov Bulka, "Java Performance and Scalability Volume 1: Server-Side Programming
    Techniques" Addison Wesley, ISBN: 0-201-70429-3 p.30 ?C 31

    二十一、尽可能的使用栈变量
    如果一个变量需要经常访问,那么你就需要考虑这个变量的作用域了。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;
    }
             
    参考资料:         
    Peter Haggar: "Practical Java - Programming Language Guide".
    Addison Wesley, 2000, pp.122 ?C 125

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

    例子:
    public class DUN {
         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 {}
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-4 22:13 , Processed in 0.463272 second(s), 48 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表