50个性能优化的细节:Java高级开发必会,来学习下吧

御匾会国际

作者:程序员的话

链接:

3e74dd09-8b10-4671-b518-7bf941e23bfd

前言:

在JAVA程序中,大多数性能问题不是JAVA语言,而是程序本身。开发可以显着提高程序性能的良好编码习惯非常重要。

●1。尝试在正确的位置使用单个案例

使用单件可以减少负载负荷,缩短装载时间,并提高装载效率。但并非所有地方都适合单身人士。简单来说,单身主要适用于以下三个方面:

首先,控制资源的使用,并通过线程同步控制资源的并发访问;

第二,控制实例的生成以实现资源保护;

第三,控制数据共享,实现多个不相关的进程或线程之间的通信,而不建立直接关联。

●2。尽量避免任意使用静态变量

当对象被定义为静态变量时,GC通常不会回收对象占用的内存,例如

f9df455566254022a0719372d6167c21

此时,静态变量b的生命周期与类A同步。如果未卸载类A,则b对象将驻留在内存中,直到程序终止。

●3。尽量避免过于频繁地创建过多的Java对象。

尽量避免频繁调用的循环中的新对象,因为系统不仅需要时间来创建对象,而且还需要时间来垃圾收集和处理这些对象,从而最大限度地重用控件中的对象。最好用基本数据类型或数组替换对象。

●4。尝试使用最终修饰符

具有final修饰符的类不可派生。在JAVA核心API中,有许多最终应用程序的示例,例如java,lang和String。为String类指定final会阻止用户覆盖length()方法。另外,如果一个类是final,那么该类的所有方法都是final。 java编译器将寻找内联所有最终方法的机会(这与特定的编译器实现有关),这可以将性能平均提高50%。

例如,让访问实例中变量的getter/setter方法变为“final:

简单的getter/setter方法应该设置为final,这告诉编译器这个方法不会被重载,所以它可以变成“内联”,例如:

34be24ee444b4d5185a899072e093cdf

●5。尝试使用局部变量

调用方法时传递的参数和调用中创建的临时变量保存在堆栈中,速度更快;其他变量(如静态变量和实例变量)是在堆(Heap)中创建的,这种变量较慢。

●6。尝试处理包类型和基本类型的使用。

尽管在使用期间包类型和基本类型可以相互转换,但是由两者生成的存储区域是完全不同的。基本类型数据生成和处理在堆栈上处理,包类型是对象,它在堆中。生成实例。在集合类对象中,需要为包类型处理对象,而其他进程则主张使用基类型。

●7。使用synchronized来最小化同步方法

众所周知,同步的实现是一个很大的系统开销,甚至可能导致死锁,所以尽量避免不必要的同步控制。调用synchronize方法时,将直接锁定当前对象。在执行该方法之前,其他线程无法调用当前对象的其他方法。因此,最小化同步方法,并且应该使用方法同步而不是代码块同步。

●9。尽量不要使用finalize方法

事实上,将资源清理放在finalize方法中是一个非常糟糕的选择。由于GC的工作负载非常大,特别是当Young内存被回收时,应用程序将暂停,因此请选择finalize方法来使用该资源。清理将导致更大的GC负担和更慢的程序操作。

●10。尝试使用基本数据类型而不是对象

3a3486a753374dd5b4f2f1d5e1835177

这种方式将创建一个“hello”字符串,JVM的字符缓存池也将缓存此字符串;

80e416917f504f4a89ed29fc93bdd756

此时,除了创建一个字符串之外,str引用的String对象还包含一个char []数组,该数组又存储h,e,l,l,o

●11。多线程应该使用HashMap,ArrayList

而不需要线程安全。

HashTable,Vector等使用同步机制来降低性能。

●12。尽可能合理地创建HashMap

当您想要创建更大的hashMap

时,请利用此构造函数

0312f1e10bd748e897680372015fe36a

在HashMap中避免多次哈希重构。扩容是一项非常昂贵的工作。在默认情况下,initialCapacity仅为16,loadFactor为0.75。需要多少容量,您可以更好地估计所需的最佳容量。大小,相同的Hashtable,Vectors是一样的。

●13。尽量减少变量的重复计算

如:

582ff5e39114432296ffd44b35e29085

应改为:

b41f68df1963447cb3976c6f7ad3c809

你应该避免在循环中使用复杂的表达式。在循环中,迭代地计算循环条件。如果不使用复杂表达式并且循环条件值相同,则程序将运行得更快。

●14。尽量避免不必要的创作

如:

f9f12632353047989d23df4ec7254fdd

应改为:

9550c906b3a34961974411fbe126f055

●15。尝试在finally块中释放资源

应该释放程序中使用的资源以避免资源泄漏,最好在finally块中完成。无论程序执行的结果如何,始终执行finally块以确保正确关闭资源。

●16。尝试使用shift而不是'a/b'操作

'/'是一项非常昂贵的操作,使用班次操作将更快更有效

如:

d80e5c52dc7e48b1aeb398749bb053b2

应改为:

cff12b93b41b422685f0d8c6591d8b0f

但请注意,使用轮班应添加注释,因为轮班操作不直观且难以理解。

●17。尝试使用shift而不是'a * b'操作

同样,对于'*'操作,使用移位操作将更快更有效

如:

893fa1ecc1a14b959f8daf2ba13813b5

应改为:

678f4192d6734ee5b39e2ae2aaaa3e0f

●18。尝试确定StringBuffer的容量

StringBuffer构造函数创建一个默认大小的字符数组(通常为16)。在使用中,如果超出此大小,将重新分配内存,将创建一个更大的数组,原始数组将被复制,旧数组将被丢弃。在大多数情况下,您可以在创建StringBuffer时指定大小,这可以在容量不足以提高性能时避免自动增长。

如:

bc2da60c93aa407ea8c54b5c88953f6f

●19。尽早释放对无用对象的引用

大多数情况下,方法本地引用变量引用的对象在方法结束时变为垃圾,因此大多数情况下程序不需要将引用变量显式设置为null。

例如:

Java代码

bcb4ef6c0a134853b6e11ce29d3112e2

以上是没有必要的。随着方法test()的执行完成,程序中obj引用变量的范围结束。但如果改为以下内容:

Java代码

b664680118174e8ba5dae7d7b3211b77

此时有必要将obj赋值为null,您可以尽快释放对Object对象的引用。

●20。尽量避免使用二维数组

二维数据比一维阵列占用更多的存储空间,大约是10倍。

●21。尽量避免使用拆分

除非有必要,否则应避免使用拆分。由于split支持正则表达式,因此效率相对较低。如果频繁是几十次,数百万次通话会消耗大量资源。如果你真的需要频繁调用split,可以考虑使用Apache的StringUtils.split(string,char),它可以经常缓存。

●22。ArrayList&链表

一个是线性表,一个是链表,一个句子,随机查询尝试使用ArrayList,ArrayList比LinkedList好,LinkedList也移动指针,添加删除操作LinkedList比ArrayList好,ArrayList也移动数据,但这个理论分析事实并非如此。重要的是要了解两者的数据结构,并规定正确的药物。

●23。尝试使用System.arraycopy()而不是循环遍历数组

System.arraycopy()比通过循环复制数组快得多。

●24。尝试缓存常用对象

尽可能缓存经常使用的对象,可以使用数组或HashMap容器进行缓存,但这种方式可能会导致系统占用过多的缓存,性能下降,建议使用一些第三方开源工具,例如EhCache Oscache被缓存,它们基本上实现了缓存算法,如FIFO/FLU。

●25。尽量避免非常大的内存分配

有时问题不是由当时堆的状态引起的,而是由分配失败引起的。分配的内存块必须是连续的,并且随着堆变得越来越丰富,找到更大的连续块变得越来越困难。

●26。谨慎使用例外

创建异常时,需要收集描述异常创建位置的堆栈跟踪。构建堆栈跟踪需要运行时堆栈的快照,这是一个重要的开销。当你需要创建一个Exception时,JVM必须说:不要移动,我想保存你现在的快照,所以暂时停止推送和弹出操作。堆栈跟踪不仅包含运行时堆栈中的一个或两个元素,还包含堆栈中的每个元素。

如果您创建了Exception,则必须付出代价,但捕获异常并不昂贵,因此您可以使用try-catch来包装核心内容。从技术上讲,您甚至可以随意抛出异常,而无需成本。抛出操作不会导致性能下降。在不预先创建异常的情况下抛出异常有点不寻常。真正的代价是创建例外。幸运的是,良好的编程习惯告诉我们,不管三七二十一,我们都不应该抛出异常。异常是针对异常情况而设计的,在使用时应牢记这些异常情况。

●27。尝试重用对象

特别是,在使用String对象时,应使用StringBuffer而不是字符串连接。由于系统不仅需要时间来生成对象,因此垃圾收集和处理这些对象可能需要一些时间。因此,生成太多对象将对程序的性能产生很大影响。

●28。不要重复初始化变量

默认情况下,当调用类的构造函数时,java将变量初始化为一个确定的值,所有对象都设置为null,整数变量设置为0,float和double变量设置为0.0,逻辑值设置为为假。当一个类派生自另一个类时,应特别注意这一点,因为当使用new关键字创建对象时,会自动调用构造函数链中的所有构造函数。

这是一张纸条。设置成员变量的初始值但需要调用其他方法时,最好放置一个方法。例如,在initXXX()中,因为直接调用方法赋值可能会抛出指针异常,因为该类尚未初始化,例如:public int state=this.getState()。

●29。在java + Oracle的应用程序开发中,嵌入在java中的SQL语言应尽可能以大写形式使用,以减少Oracle解析器的解析负担。

●30。在java编程过程中,数据库连接,I/O流操作,使用后,及时关闭以释放资源。因为这些大对象的操作会导致系统的大量开销。

●31。过多的对象创建会占用系统中的大量内存。在严重的情况下,它会导致内存泄漏。因此,确保及时恢复过期的对象非常重要。 JVM的GC不是很聪明,因此建议在使用对象后手动将其设置为null。

●32。使用同步机制时,尝试使用方法同步而不是代码块同步。

●33。不要在循环中使用Try/Catch语句,将Try/Catch放在最外层的循环中

Error是一个获取系统错误的类,或者是一个虚拟机错误的类。并非所有错误都可以获得异常,虚拟机无法获取错误异常,必须使用Error获取。

●34。设置StringBuffer构造函数的初始化容量可以显着提高性能

StringBuffer的默认容量为16.当StringBuffer的容量达到最大容量时,它将容量增加到2倍+2,即2 * n + 2。每当StringBuffer达到其最大容量时,它必须创建一个新的对象数组,然后复制旧的对象数组,这会浪费很多时间。因此有必要为StringBuffer设置合理的初始化容量值!

●35。合理使用java.util.Vector

Vector类似于StringBuffer,每次扩展容量时,所有现有元素都将分配给新的存储空间。 Vector的默认存储容量为10个元素,扩展量加倍。

Vector.add(index,obj)此方法将元素obj插入索引位置,但索引和后续元素依次向下移动一个位置(将其索引添加到1)。除非必要,否则表现不佳。相同的规则适用于remove(int index)方法,删除此向量中指定位置的元素。将所有后续元素向左移动(将其索引减1)。返回从此向量中删除的元素。因此,删除向量的最后一个元素比删除第一个元素要便宜得多。最好使用removeAllElements()方法删除所有元素。

如果要删除向量中的元素,可以使用vector.remove(obj);而不是自己检索元素的位置,删除它,如int index=indexOf(obj); vector.remove(index)。

●38。创建没有新关键字

的对象实例

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

以下是Factory模式的典型实现:

e1a7f74f5efe472a9d4d1bfe60df199f

改进的代码使用clone()方法:

0b743194a4d942b68680eb61e0692c8a

●39。不要将数组声明为:public static final

●40。HaspMap遍历:

c891c9a5a6d6402fa632955d0d48f544

使用哈希值获取相应的Entry并比较它以获得结果。获取条目的值后,直接获取键和值。

●41。使用数组和ArrayList

阵列阵列效率最高,但容量是固定的,不能动态更改。 ArrayList容量可以动态增长,但代价是效率。

●42。单线程应该尝试使用HashMap,ArrayList,除非有必要,不建议使用HashTable,Vector,它们使用同步机制,这会降低性能。

●43。StringBuffer和StringBuilder之间的区别在于

java.lang.StringBuffer线程安全的可变字符序列。类似于String的字符串缓冲区,但不能修改。与此类相比,StringBuilder通常更喜欢StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步。

为了获得更好的性能,请在构造StringBuffer或StringBuilder时尝试指定其容量。当然,如果你不超过16个字符,你将不需要它。在相同的情况下,使用StringBuilder只能比使用StringBuffer实现10%~15%的性能提升,但是多线程和不安全是有风险的。仍然建议使用StringBuffer。

●44。尝试使用基本数据类型而不是对象。

●45。使用特定的模拟接口是有效的,但结构灵活性降低了,但现代IDE可以解决这个问题。

●46。考虑使用静态方法,如果您不必访问对象的外部,则将方法设为静态方法。它将被更快地调用,因为它不需要虚函数来指导表。这也是一个很好的做法,因为它告诉你如何区分方法的性质。调用此方法不会更改对象的状态。

●47。应尽可能避免固有的GET,SET方法。

●48。避免枚举,使用浮点数。

以下是一些实际优化的例子:

●首先,避免在循环条件中使用复杂表达式

在没有编译优化的情况下,循环条件在循环中迭代计算。如果在不使用复杂表达式的情况下未更改循环条件值,则程序将运行得更快。例如:

b3fed5082e8f4ff3a19baa3736636d5c

校正:

e292bdd378fd49cba7a89680fc852499

●其次,定义“向量”和“哈希表”的初始大小

当JVM扩展Vector的大小时,您需要重新创建一个更大的数组,复制原始数组的内容,最后,回收原始数组。可以看出,Vector容量的扩展是一项耗时的任务。

通常,默认的10个元素大小是不够的。您最好能够准确估计所需的最佳尺寸。例如:

d33d6c44c687441697aab188eb71f286

校正:

设置您自己的初始大小。

fda8ad0b859141b6adc7cf96bd333944

●第三,关闭finally块中的Stream

应该释放程序中使用的资源以避免资源泄漏。这最好在finally块中完成。无论程序执行的结果如何,始终执行finally块以确保正确关闭资源。

●第四,使用'System.arraycopy()'而不是循环遍历数组

例如:

646ec34c2df04b95bc638f737e44a62c

校正:

9df6a8232c014ee686c846b27ac48041

●5。让访问实例中变量的getter/setter方法变为“final”

简单的getter/setter方法应该设置为final,这告诉编译器这个方法不会被重载,所以它可以变成“内联”,例如:

7f5e6db769414c0e827db5ed23188aa6

校正:

cd1738337b25444ba9aad36d63bfbd1f

●六,对于常量字符串,使用'String'而不是'StringBuffer'

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

例如:

af828cd2d3164720bb2cdbdd24ef77dd

更正:用String替换StringBuffer。如果确定此String不会更改,这将减少运行开销并提高性能。

●7。添加字符串时,如果字符串只有一个字符

,请使用''代替''

例如:

4ed3471da4b943a3b3f4ada26b4688bd

校正:

用''

替换一个字符的字符串

efb16fad56ac46d9af41b5d050f7a662

以上只是Java编程中的性能优化。大多数性能优化是在时间,效率,代码结构等方面,每个都有自己的优缺点。不要将上述内容视为教条,也许有些适用于我们的实际工作,有些不适用,也希望根据实际工作场景进行权衡,学习和使用,以及适应。

注意作者的私信,关键词:“数据”

c056fa6f044545858b8437b1398f6cfd

您可以获得Java高级学习包,主要包括:( Java工程,分布式架构,高并发,高性能,深入解释,微服务架构,Spring,MyBatis,Netty,源代码分析,JVM原理分析,这些是架构师必备的)和Java高级学习路线图。

记得转发它!