19个小细节,让你提升Java代码的运行效率

2022-11-18 14:54:06 浏览数 (1)

1.使用局部变量可避免在堆上分配

由于堆资源是多线程共享的,是垃圾回收器工作的主要区域,过多的对象会造成 GC 压力。可以通过局部变量的方式,将变量在栈上分配。这种方式变量会随着方法执行的完毕而销毁,能够减轻 GC 的压力。

2.削弱变量的作用范围

注意变量的作用范围,尽量减少对象的创建。如下面的代码,变量 a 每次进入方法都会创建,可以将它移动到 if 语句内部。

3.使用类名方式访问静态变量

有的同学习惯使用对象访问静态变量,这种方式多了一步寻址操作,需要先找到变量对应的类,再找到类对应的变量。

4.字符串拼接不要使用 ” ”

字符串拼接,使用 StringBuilder或者StringBuffer,不要使用 号。

5.重写对象的HashCode,不要简单地返回固定值

开发时重写 HashCodeEquals 方法时,会把 HashCode的值返回固定的 0,而这样做是不恰当的。

当这些对象存入 HashMap 时,性能就会非常低,因为 HashMap是通过 HashCode 定位到 Hash 槽,有冲突的时候,才会使用链表或者红黑树组织节点。固定地返回 0,相当于把 Hash 寻址功能给废除了。

6.HashMap等集合初始化的时候,尽量指定初始值大小

通过指定初始值大小可减少扩容造成的性能损耗。

7.遍历Map 的时候,使用 EntrySet 方法

使用 EntrySet 方法,可以直接返回 set 对象,直接拿来用即可;而使用 KeySet 方法,获得的是key 的集合,需要再进行一次 get 操作,多了一个操作步骤。所以更推荐使用 EntrySet 方式遍历 Map。

8.不要在多线程下使用同一个 Random

Random 类的 seed 会在并发访问的情况下发生竞争,造成性能降低,建议在多线程环境下使用 ThreadLocalRandom 类。

在 Linux 上,通过加入 JVM 配置 -Djava.security.egd=file:/dev/./urandom,使用 urandom 随机生成器,在进行随机数获取时,速度会更快。

9.自增推荐使用 LongAddr

自增运算可以通过 synchronized 和 volatile 的组合,或者也可以使用原子类(比如 AtomicLong)。

后者的速度比前者要高一些,AtomicLong 使用 CAS 进行比较替换,在线程多的情况下会造成过多无效自旋,所以可以使用 LongAdder 替换 AtomicLong 进行进一步的性能提升。

10.不要使用异常控制程序流程

异常,是用来了解并解决程序中遇到的各种不正常的情况,它的实现方式比较昂贵,比平常的条件判断语句效率要低很多。

这是因为异常在字节码层面,需要生成一个如下所示的异常表(Exception table),多了很多判断步骤。

11.不要在循环中使用 try catch

道理与上面类似,很多文章介绍,不要把异常处理放在循环里,而应该把它放在最外层,但实际测试情况表明这两种方式性能相差并不大。

既然性能没什么差别,那么就推荐根据业务的需求进行编码。比如,循环遇到异常时,不允许中断,也就是允许在发生异常的时候能够继续运行下去,那么异常就只能在 for 循环里进行处理。

12.不要捕捉 RuntimeException

Java 异常分为两种,一种是可以通过预检查机制避免的 RuntimeException;另外一种就是普通异常。

其中,RuntimeException 不应该通过 catch 语句去捕捉,而应该使用编码手段进行规避。

13.合理使用 PreparedStatement

PreparedStatement 使用预编译对 SQL 的执行进行提速,大多数数据库都会努力对这些能够复用的查询语句进行预编译优化,并能够将这些编译结果缓存起来。

这样等到下次用到的时候,就可以很快进行执行,也就少了一步对 SQL 的解析动作。

PreparedStatement 还能提高程序的安全性,能够有效防止 SQL 注入。

但如果你的程序每次 SQL 都会变化,不得不手工拼接一些数据,那么 PreparedStatement 就失去了它的作用,反而使用普通的 Statement 速度会更快一些。

14.日志打印的注意事项

debug 输出一些调试信息,然后在线上关掉它。

15.减少事务的作用范围

如果的程序使用了事务,那一定要注意事务的作用范围,尽量以最快的速度完成事务操作。这是因为,事务的隔离性是使用锁实现的。

16.使用位移操作替代乘除法

计算机是使用二进制表示的,位移操作会极大地提高性能。

  • << 左移相当于乘以 2;
  • << 右移相当于除以 2;
  • >>>无符号右移相当于除以 2,但它会忽略符号位,空位都以 0 补齐。

17.不要打印大集合或者使用大集合的 toString 方法

有的开发喜欢将集合作为字符串输出到日志文件中,这个习惯是非常不好的。

拿 ArrayList 来说,它需要遍历所有的元素来迭代生成字符串。在集合中元素非常多的情况下,这不仅会占用大量的内存空间,执行效率也非常慢

18.尽量少在程序中使用反射

反射的功能很强大,但它是通过解析字节码实现的,性能就不是很理想。

现实中有很多对反射的优化方法,比如把反射执行的过程(比如 Method)缓存起来,使用复用来加快反射速度。

Java 7.0 之后,加入了新的包 java.lang.invoke,同时加入了新的 JVM 字节码指令 invokedynamic,用来支持从 JVM 层面,直接通过字符串对目标方法进行调用。

如果你对性能有非常苛刻的要求,则使用 invoke 包下的 MethodHandle 对代码进行着重优化,但它的编程不如反射方便,在平常的编码中,反射依然是首选。

19.正则表达式可以预先编译,加快速度

Java 的正则表达式需要先编译再使用。

0 人点赞