当前位置: 首页 > 科技观察

Java程序员优化编程性能的34个技巧

时间:2023-03-13 08:53:28 科技观察

1。尽量在合适的场合使用单例使用单例可以减轻加载负担,缩短加载时间,提高加载效率,但并不是所有地方都适用单例。简单来说,单例主要适用于以下三个方面:控制资源的使用,通过线程同步控制资源的并发访问;控制实例的生成,达到节省资源的目的;控制数据共享,以启用多个不相关的进程或线程之间的通信,而无需建立直接关联。2、尽量避免随意使用静态变量。要知道,当一个对象被定义为静态变量引用时,GC通常不会回收该对象占用的内存。同步的话,如果classA不会被卸载,那么objectb会一直留在内存中,直到程序终止。3、尽量避免Java对象的创建过多和过多尽量避免在频繁调用的方法和循环中新建对象,因为系统不仅要花时间创建对象,还要花时间对这些对象进行垃圾收集和处理。在我们能控制的范围内,尽可能复用对象,可以用基本数据类型或数组来代替对象。4.尽量使用final修饰符带有final修饰符的类是不可导出的。在JAVA核心API中,有很多应用final的例子,比如java.lang.String,为String类指定final,防止用户重写length方法。此外,如果一个类是最终的,则该类的所有方法都是最终的。java编译器会寻找机会内联(inline)所有的final方法(这个和具体的编译器实现有关)。这可以将性能平均提高50%。例如:将实例中访问变量的getter/setter方法设置为final:简单的getter/setter方法应该设置为final,这样会告诉编译器这个方法不会被重载,所以可以变成“inlined”"",例子:5.尽量使用局部变量。调用方法时传递的参数和调用过程中创建的临时变量存储在栈(Stack)中,速度更快。其他变量,如静态变量、实例变量等,都是在堆(Heap)中创建的,速度较慢。6.尽量处理包装类型和基本类型的使用。虽然封装类型和基本类型在使用过程中可以相互转换,但是两者产生的内存区域是完全不同的。基本类型数据的生成和处理都在栈中处理,包装类型为对象,在堆中生成实例。集合类对象中,对象需要的处理适用于包装类型,其他处理提倡使用基本类型。7、谨慎使用synchronized,尽量减少synchronize的方法。我们都知道实现同步需要大量的系统开销作为代价,甚至可能造成死锁,所以尽量避免不必要的同步控制。当调用synchronize方法时,会直接锁定当前对象,其他线程在该方法执行前不能调用当前对象的其他方法。所以synchronize的方法越小越好,尽量使用方法同步而不是代码块同步。8、尽量不要使用finalize方法。事实上,在finalize方法中完成资源清理是一个非常糟糕的选择。由于GC的工作量大,尤其是在回收Young代内存的时候,大部分的应用都会被挂起,所以然后选择使用finalize的方式来清理资源,这样会导致GC的负担更大,更糟糕的程序运行效率。9.尽量使用基本数据类型而不是对象Stringstr="hello";上面的方法会创建一个“hello”字符串,JVM的字符缓冲池也会缓存这个字符串;Stringstr=newString("hello”);这时候str引用的String对象底层除了创建了一个字符串外,还包含了一个char数组,里面存放了h,e,l,l,o10sequence.多线程在线程安全的前提下不发生应该尽量使用同步机制的HashMap,ArrayListHashTable,Vector等,降低性能.11.尽量合理的创建HashMap.当你想创建一个比较大的hashMap,充分利用这个构造函数,避免对HashMap进行多次哈希重构。扩容是一个很耗性能的事情。默认的initialCapacity只有16。而loadFactor是0.75,需要多少容量,可以准确估计你需要的最大尺寸,Hashtable和Vectors同样如此。12.尽量减少变量的重复计算。在循环中,你应该避免使用复杂的表达式。在循环中,循环条件会被重复计算。如果你do不使用复杂的表达式,保持循环条件的值不变,程序会运行得更快。13.尝试在finally块中释放资源。应释放程序中使用的资源,以避免资源泄漏。这最好在finally块中完成。不管程序执行的结果如何,finally块总是会被执行,以确保资源被正确关闭。14.尝试使用shift代替'a/b'操作“/”是一个非常昂贵的操作,使用shift的操作会更快更高效15.尝试确定StringBuffer的容量StringBuffer的构造函数会创建一个字符数组默认大小(通常为16)。在使用中,如果超过这个大小,就会重新分配内存,创建一个更大的数组,复制原来的数组,丢弃旧的数组。大多数情况下,可以在创建StringBuffer时指定大小,避免容量不够时自动增长,提高性能。16.当大部分无用对象的引用被尽早释放时,方法局部引用变量所引用的对象会随着方法结束而变成垃圾。因此,大多数时候程序不需要显式地将局部引用变量设置为空。例如:以上是不必要的,随着方法test的执行完成,程序中obj引用变量的作用域结束。但是如果改成下面这样:这时候就需要将obj赋值给null,尽快释放对Object对象的引用。17、尽量避免使用二维数组。二维数据占用的内存空间比一维数组大得多,大约多10倍。18、非必要尽量避免使用split,否则应避免使用split。由于split支持正则表达式,所以效率比较低。如果是频繁的几十上百万次调用会消耗很多资源,如果真的需要频繁调用split,可以考虑使用Apache的StringUtils.split(string,char),频繁split的结果可以缓存起来.19.ArrayList&LinkedList一个是线性表,一个是链表。总之,随机查询尽量使用ArrayList。ArrayList优于LinkedList。LinkedList也需要移动指针。LinkedList在增删改操作上优于ArrayList。ArrayList也需要移动数据,但这是理论上的分析,不一定如此。重要的是了解两者的数据结构,对症下药。20.尝试使用System.arraycopy而不是循环复制数组。System.arraycopy比循环复制数组要快得多。21.尽量缓存经常使用的对象。你可以使用数组或者HashMap容器来进行缓存,但是这种方式可能会导致系统占用过多的缓存而降低性能。推荐使用一些第三方开源工具,如EhCache、Oscache等进行缓存。他们基本上实现了FIFO/FLU等缓存算法。22.尽量避免分配非常大的内存。有时问题不是堆当时的状态引起的,而是分配失败引起的。所有分配的内存块必须是连续的,随着堆越来越满,找到更大的连续块变得越来越困难。23.谨慎使用异常创建异常时,需要收集堆栈跟踪(stacktrack),用于描述异常创建的位置。构建这些堆栈跟踪需要拍摄运行时堆栈的快照,而这部分成本很高。当需要创建Exception时,JVM不得不说:别动,我要保存你现在的快照,所以暂时停止push和pop操作。堆栈跟踪不仅包括运行时堆栈中的一个或两个元素,还包括该堆栈中的每个元素。如果你创建了一个Exception,你就要付出代价。好在捕获异常的开销不大,可以用try-catch来包裹核心内容。从技术上讲,您甚至可以随意抛出异常而无需付出太多代价。引发性能损失的不是throw操作——尽管在没有预先创建异常的情况下抛出异常有点不寻常。真正的成本是创建异常。幸运的是,良好的编程习惯告诉我们无论在什么情况下都不应该抛出异常。异常是为异常情况而设计的,使用时应牢记这一原则。24、尽量重用对象,尤其是在String对象的使用中,出现字符串拼接时应该使用StringBuffer代替,因为系统不仅需要时间生成对象,还可能需要时间对这些对象进行垃圾回收和处理未来。因此,生成过多的对象会对程序的性能产生很大的影响。25.不要重复初始化变量默认情况下,在调用类的构造函数时,java会将变量初始化为某个值,所有对象都设置为null,整型变量设置为0,float和double变量设置为0.0,logic的值设置为false。当一个类派生自另一个类时尤其如此,因为当使用new关键字创建对象时,会自动调用构造函数链中的所有构造函数。这里有一个注释。当为成员变量设置初始值但需要调用其他方法时,最好放在initXXX等方法中,因为直接调用方法赋值可能会因为类还没有初始化而抛出空指针异常,比如如:publicintstate=this.getState;26、在java+Oracle应用系统开发中在java+Oracle应用系统开发中,java中嵌入的SQL语言尽量使用大写,以减轻Oracle解析器的分析负担。27、I/O流操作在java编程过程中,进行数据库连接和I/O流操作,使用后及时关闭,释放资源。因为对这些大对象的操作会造成很大的系统开销。28、创建对象会消耗大量系统内存过多地创建对象会消耗系统大量内存,严重时会导致内存泄漏。因此,保证过期对象的及时回收具有重要意义。JVM的GC不是很智能,所以建议在对象使用完后手动设置为null。29、使用同步机制时,在使用同步机制时,尽量使用方法同步,而不是代码块同步。30、不要在循环中使用Try/Catch语句。Try/Catch应该放在循环的最外层。error是获取系统错误的类,或者说是虚拟机错误的类。并不是所有的错误Exception都可以获得。如果虚拟机报错Exception,则无法获取。必须使用错误来获取它。31、通过其构造函数设置StringBuffer的初始化容量可以显着提高性能。StringBuffer的默认容量为16,当StringBuffer的容量达到最大容量时,它会将自身容量增加到当前容量的2倍+2,即2*n+2。每当StringBuffer达到她的最大容量时,她必须创建一个新的对象数组,然后复制旧的对象数组,这会浪费很多时间。所以有必要给StringBuffer设置一个合理的初始容量值!32、java.util.VectorVector的合理使用类似于StringBuffer。每次扩容时,都必须将现有的所有元素分配到新的存储空间中。Vector默认存储容量为10个元素,扩展容量翻倍。vector.add(index,obj)该方法可以在index位置插入元素obj,但是index和后面的元素必须依次向下移动一位(其index加1)。除非必要,否则对性能不利。相同的规则适用于remove(intindex)方法,该方法删除此向量中指定位置的元素。将所有后续元素向左移动(将其索引减1)。返回此向量中删除的元素。所以删除vector的第一个元素比删除第一个元素便宜得多。要删除所有元素***使用removeAllElements方法。如果你想删除vector中的一个元素,你可以使用vector.remove(obj);而不是自己检索元素的位置,然后删除,比如intindex=indexOf(obj);vector.remove(索引);33.不使用new关键字创建当一个对象的实例使用new关键字创建一个类的实例时,构造函数链中的所有构造函数都会被自动调用。但是如果一个对象实现了Cloneable接口,我们就可以调用她的clone方法。clone方法不调用任何类构造函数。下面是Factory模式的一个典型实现:34、HaspMap的遍历使用hash值提取对应的Entry进行比较得到结果,得到entry的value后直接得到key和value。