提升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编程时的性能优化。大多数性能优化都是在时间、效率、代码结构层次等方面进行权衡,各有利弊。不要把以上内容当作教条。有的可能适用于我们的实际工作,有的可能适用于我们的实际工作。不适用。我们希望根据实际工作场景做出选择,学以致用,进行相应的调整。
来源:网络,原作者不详。如果有人知道请留言。