提升JAVA程序性能的关键:养成良好的编码习惯与单例模式应用

在JAVA程序中,大部分性能问题的原因并不在于JAVA语言,而在于程序本身。养成良好的编码习惯非常重要,可以显着提高程序性能。

提升JAVA程序性能的关键:养成良好的编码习惯与单例模式应用

1.在适当的情况下尝试使用单例

使用单例可以减轻加载负担,缩短加载时间,提高加载效率,但并不是所有地方都适用单例。简单来说,单例主要适用于以下三个方面:

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

控制实例的生成,节省资源;

控制数据共享并允许多个不相关的进程或线程之间进行通信,而无需建立直接关系。

2.尽量避免随便使用静态变量

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

public class A{

private static B b = new B();

}

此时,静态变量b的生命周期与A类同步。如果A类不被卸载,b对象就会一直保留在内存中,直到程序终止。

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

尽量避免在频繁调用的方法和循环中使用新对象。由于系统不仅要花时间创建对象,还要花时间对这些对象进行垃圾回收和处理,因此我们应该在我们控制的范围内最大限度地重用对象。 ,最好使用基本数据类型或数组来代替对象。

4.尝试使用final修饰符

带有final修饰符的类不会被派生。在JAVA核心API中,有很多使用final的例子,比如java、lang、String等。为 String 类指定 Final 可以防止用户重写 length() 方法。另外,如果一个类是final的,那么该类的所有方法都是final的。 Java编译器会寻找机会内联所有final方法(这取决于具体的编译器实现),这可以平均提高50%的性能。

例如:将访问实例中变量的 getter/setter 方法设置为“final”:

简单的 getter/setter 方法应该是最终的。这告诉编译器该方法不会被重载,因此可以将其设置为“内联”。例子:

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;

}

5. 尝试使用局部变量

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

6、尽量处理好包装型和基本型的使用场所。

虽然包装类型和基本类型在使用过程中可以相互转换,但是它们生成的内存区域却完全不同。基本类型数据在堆栈中生成和处理,而包装类型是对象并存储在堆中。生成实例。在集合类对象中,包装类型适合需要对象方面的处理,其他处理建议使用基本类型。

7. 谨慎使用synchronized,尽量减少synchronized方法的数量。

我们都知道,实现同步需要很大的系统开销,甚至可能会造成死锁,所以尽量避免不必要的同步控制。调用synchronize方法时,直接锁定当前对象,在该方法执行完毕之前,其他线程无法调用当前对象的其他方法。因此,应尽量减少synchronize方法,并尽可能使用方法同步而不是代码块同步。

8.尽量不要使用finalize方法

事实上,在finalize方法中完成资源清理是一个非常糟糕的选择。由于GC的工作量很大,尤其是在回收Young代内存时,往往会导致应用程序暂停,所以选择使用finalize方法来清理资源。清理会导致更大的GC负担和更差的程序运行效率。

9. 尝试使用基本数据类型而不是对象

String str = "hello";

上面的方法会创建一个“hello”字符串,JVM的字符缓冲池也会缓存这个字符串;

String str = new String("hello");

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

10、多线程应尽量使用没有线程安全的HashMap和ArrayList。

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

11、尽可能合理地创建HashMap

当你想创建一个比较大的hashMap时,充分利用这个构造函数

public HashMap(int initialCapacity, float loadFactor);

避免HashMap的多次哈希重构。扩容是一件非常消耗性能的事情。默认情况下,initialCapacity仅为16,loadFactor为0.75。需要多少容量?您最好准确估计您需要的最佳容量。大小、Hashtable 相同、Vectors 相同。

12.减少变量的双重计算

喜欢:

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

应改为:

for(int i=0,len=list.size();i

并且应该避免在循环中使用复杂的表达式。在循环中,循环条件会被重复计算。如果不使用复杂的表达式并且循环条件值保持不变,程序将运行得更快。

13.尽量避免不必要的创作

喜欢:

A a = new A();

if(i==1){

list.add(a);

}

应改为:

if(i==1){

A a = new A();

list.add(a);

}

14.尝试在finally块中释放资源

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

15. 尝试使用轮班而不是“a/b”操作

“/”是一个昂贵的操作,使用移位操作会更快更高效

喜欢:

int num = a / 4;

int num = a / 8;

应改为:

int num = a >> 2;

int num = a >> 3;

但需要注意的是,使用移位时应添加注释,因为移位操作不直观且难以理解。

16.尝试使用移位代替'a*b'操作

同样,对于“*”操作,使用移位操作会更快、更高效

喜欢:

int num = a * 4;

int num = a * 8;

应改为:

int num = a << 2;

int num = a << 3;

17.尝试确定StringBuffer的容量

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

喜欢:

StringBuffer buffer = new StringBuffer(1000);

18. 尽早释放无用对象的引用

大多数时候,方法的局部引用变量所引用的对象在方法结束时就会变成垃圾。因此,大多数时候,程序不需要显式地将局部引用变量设置为null。

例如:

Java代码

Public void test(){

Object obj = new Object();

……

Obj=null;

}

以上是不必要的。当方法test()执行完成后,程序中obj引用变量的作用域就结束了。但如果改成如下:

Java代码

Public void test(){

Object obj = new Object();

……

Obj=null;

//执行耗时,耗内存操作;或调用耗时,耗内存的方法

……

}

这时候就需要将obj赋值为null,这样才能尽早释放Object对象的引用。

19.尽量避免使用二维数组

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

20.尽量避免使用split

除非必要,否则应避免使用 split。 split支持正则表达式,所以效率比较低。频繁的几十上百万的调用会消耗大量的资源。如果确实需要频繁调用split,可以考虑使用apache的StringUtils.split(string,char)。频繁的分割可以缓存结果。

21. 数组列表和链表

一种是线性表,另一种是链表。总之,尽量使用ArrayList进行随机查询。 ArrayList 比 LinkedList 更好。 LinkedList也需要移动指针。 LinkedList 在添加和删除操作方面比 ArrayList 更好。 ArrayList也需要移动数据。然而,这是理论上的分析。 ,情况可能并非如此。重要的是了解两者的数据结构,对症下药。

22.尝试使用System.arraycopy()而不是循环来复制数组

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

23. 尝试缓存经常使用的对象

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

24. 尽量避免非常大的内存分配

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

25. 谨慎使用异常

当创建异常时,需要收集堆栈跟踪,它描述了异常的创建位置。构建这些堆栈跟踪需要获取运行时堆栈的快照,而这部分的成本很高。当需要创建异常时,JVM 必须说:先别动,我想保存你现在的样子的快照,所以暂时停止推入和弹出操作。堆栈跟踪不仅包含运行时堆栈中的一两个元素,还包含堆栈中的每个元素。

如果创建了Exception,就要付出代价,但是捕获异常并不昂贵,所以可以使用try-catch来包装核心内容。从技术上讲,您甚至可以随意抛出异常,而无需付出巨大的代价。引发性能损失的并不是抛出操作——尽管在没有首先创建异常的情况下抛出异常有点不寻常。真正的成本是创造例外。幸运的是,良好的编程习惯告诉我们,我们不应该不顾一切地抛出异常。异常是为不寻常的情况而设计的,使用它们时应牢记这一原则。

26.尽可能重复使用对象

特别是使用String对象时,发生字符串串联时应该使用StringBuffer来代替。由于系统不仅要花时间生成对象,将来可能还需要花时间垃圾收集和处理这些对象。因此,生成过多的对象会对程序的性能产生很大的影响。

27.不要重复初始化变量

默认情况下,当调用类的构造函数时,Java会将变量初始化为某些值,所有对象都设置为null,整型变量设置为0,float和double变量设置为0.0,逻辑值设置为错误的。当一个类派生于另一个类时,这一点尤其要注意,因为当使用new关键字创建对象时,构造函数链中的所有构造函数都会被自动调用。

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

28、在开发Java+Oracle应用系统时,Java中嵌入的SQL语言应尽量采用大写,以减轻Oracle解析器的解析负担。

29、Java编程过程中,进行数据库连接和I/O流操作。使用完毕后及时关闭,释放资源。因为对这些大对象的操作会造成很大的系统开销。

30、过多的对象创建会消耗大量的系统内存。严重时会导致内存泄漏。因此,保证过期物品的及时回收具有重要意义。 JVM的GC不是很智能,所以建议在对象使用完后手动将其设置为null。

31、使用同步机制时,尽量使用方法同步,而不是代码块同步。

32.不要在循环中使用Try/Catch语句。 Try/Catch 应该放在循环的最外层。

Error 是一个用于获取系统错误或虚拟机错误的类。并不是所有的错误异常都能获取到。如果虚拟机报错Exception,则无法获取。您必须使用 Error 来获取它。

33、通过StringBuffer的构造函数设置StringBuffer的初始容量可以显着提高性能。

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

34.正确使用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);矢量.删除(索引)。

35、不使用new关键字创建对象的实例

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

下面是工厂模式的典型实现:

public static Credit getNewCredit()

{

return new Credit();

}

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

private static Credit BaseCredit = new Credit();

public static Credit getNewCredit()

{

return (Credit)BaseCredit.clone();

}

36. 不要将数组声明为:public static final

37.HaspMap的遍历:

Map paraMap = new HashMap();

for( Entry entry : paraMap.entrySet() )

{

String appFieldDefId = entry.getKey();

String[] values = entry.getValue();

}

使用hash值检索对应的Entry进行比较,得到结果。获取入口值后,直接获取key和value。

38.数组(array)和ArrayList的使用

array 数组效率最高,但其容量是固定的,不能动态改变。 ArrayList的容量可以动态增加,但是牺牲了效率。

39、单线程应尽量使用HashMap和ArrayList。除非必要,不建议使用HashTable和Vector。它们使用同步机制并降低性能。

40、StringBuffer和StringBuilder的区别是:java.lang.StringBuffer是线程安全的可变字符序列。与String类似的字符串缓冲区,但不能修改。 StringBuilder 通常应优先使用 StringBuilder 类,因为它支持所有相同的操作,但速度更快,因为它不执行同步。为了获得更好的性能,你应该尽量在构造StringBuffer或StringBuilder时指定她的容量。当然,如果不超过16个字符就不必了。同等情况下,使用StringBuilder只能比使用StringBuffer实现10%到15%的性能提升,但会存在多线程不安全的风险。综合考虑,建议使用StringBuffer。

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

42. 使用具体类比比使用接口更高效,但结构灵活性降低,但现代 IDE 可以解决这个问题。

43.考虑使用静态方法。如果您不需要访问对象的外部,那么请将您的方法设为静态。因为不需要虚函数引导表,所以调用速度会更快。这也是一个很好的实践,因为它告诉你如何区分方法的性质,调用这个方法不会改变对象的状态。

44. 应尽可能避免使用内在的 GET 和 SET 方法。

45.避免使用枚举和浮点数。

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

1.避免在循环条件中使用复杂的表达式

如果不进行编译优化,循环条件将在循环中重复计算。如果不使用复杂的表达式并且循环条件值保持不变,程序将运行得更快。例子:

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++)

; // ...

}

}

2. 定义“向量”和“哈希表”的初始大小

当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);

3. 在finally块中关闭Stream

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

4.使用'System.arraycopy()'而不是循环来复制数组

例子:

public class IRB

{

void method () {

int[] array1 = new int [100];

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

array1 [i] = i;

}

int[] array2 = new int [100];

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

array2 [i] = array1 [i]; // Violation

}

}

}

正确的:

public class IRB

{

void method () {

int[] array1 = new int [100];

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

array1 [i] = i;

}

int[] array2 = new int [100];

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

}

}

5. 将访问实例内变量的 getter/setter 方法设置为“final”

简单的 getter/setter 方法应该是最终的。这告诉编译器该方法不会被重载,因此可以将其设置为“内联”。例子:

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;

}

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

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

例子:

public class USC {

String method () {

StringBuffer s = new StringBuffer ("Hello");

String t = s + "World!";

return t;

}

}

更正:将 StringBuffer 替换为 String。如果你确定String不会再次改变,这将减少操作开销并提高性能。

7. 添加字符串时,如果字符串只有一个字符,请使用“ ”而不是“ ”。

例子:

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'

}

}

以上只是Java编程时的性能优化。大多数性能优化都是在时间、效率、代码结构层次等方面进行权衡,各有利弊。不要把以上内容当作教条。有的可能适用于我们的实际工作,有的可能适用于我们的实际工作。不适用。我们希望根据实际工作场景做出选择,学以致用,进行相应的调整。

来源:网络,原作者不详。如果有人知道请留言。

你可能想看:
« 上一篇
下一篇 »

相关推荐

java中什么是冒泡,冒泡算法

2026年09月06日

27阅读

java中冒号,java双冒号

2026年09月06日

26阅读

java中的->,java中的是什么意思

2026年09月06日

24阅读

java中的super用法,Java中super的说明

2026年09月06日

28阅读

java中反射的应用,java中反射的应用场景

2026年09月06日

26阅读

java中开关的定义,Java关键字有哪些

2026年09月06日

26阅读