java性能优化

Leo_wlCnBlogs      2022-04-11     723

关键词:

Java性能优化之编程技巧总结

https://www.cnblogs.com/yueshutong/p/9555174.html

程序的性能受代码质量的直接影响。在本文中,主要介绍一些代码编写的小技巧和惯例,这些技巧有助于在代码级别上提升系统性能。

1、慎用异常
在Java软件开发中,经常使用 try-catch 进行错误捕获,但是,try-catch 语句对系统性能而言是非常糟糕的。虽然在一次 try-catch中,无法察觉到它对性能带来的损失,但是,一旦try-catch被应用于循环之中,就会给系统性能带来极大的伤害。

以下是一段将try-catch应用于for循环内的示例

public void test() {
int a = 0;
for (int i = 0; i < 1000000; i++) {
try {
a = a + 1;
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
}
}
}
这段代码我运行时间是 27211 ms。如果将try-catch移到循环体外,那么就能提升系统性能,如下代码

public void test() {
    int a = 0;
    try {
        for (int i = 0; i < 1000000; i++) {
            a = a + 1;
            System.out.println(i);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

运行耗时 15647 ms。可见tyr-catch对系统性能的影响。

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

下面是一段测试用例

// private static int a = 0;

public static void main(String[] args) {
    int a = 0;
    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        a = a + 1;
        System.out.println(i);
    }
    System.out.println(System.currentTimeMillis() - start);
}

运行结果很明显,使用静态变量耗时15677ms,使用局部变量耗时13509ms。由此可见,局部变量的访问速度高于类的成员变量。

3、位运算代替乘除法
在所有的运算中,位运算是最为高效的。因此,可以尝试使用位运算代替部分算术运算,来提高系统的运行速度。

比如在HashMap的源码中使用了位运算

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

static final int MAXIMUM_CAPACITY = 1 << 30;

对于整数的乘除运算优化

a*=2
a/=2
用位运算可以写为

a<<=1
a>>=1
4、替换switch
关键字 switch 语句用于多条件判断, switch 语句的功能类似于 if-else 语句,两者性能也差不多。因此,不能说 switch 语句会降低系统的性能。但是,在绝大部分情况下,switch 语句还是有性能提升空间的。

来看下面的例子:

public static void main(String[] args) {
    long start = System.currentTimeMillis();
    int re = 0;
    for (int i = 0;i<1000000;i++){
        re = switchInt(i);
        System.out.println(re);
    }
    System.out.println(System.currentTimeMillis() - start+"毫秒");//17860
}

public static int switchInt(int z){
    int i = z%10+1;
    switch (i){
        case 1:return 3;
        case 2:return 6;
        case 3:return 7;
        case 4:return 8;
        case 5:return 10;
        case 6:return 16;
        case 7:return 18;
        case 8:return 44;
        default:return -1;
    }
}

就分支逻辑而言,这种 switch 模式的性能并不差。但是如果换一种新的思路替代switch,实现相同的程序功能,性能就能有很大的提升空间。

public static void main(String[] args) {
    long start = System.currentTimeMillis();
    int re = 0;
    int[] sw = new int[]{0,3,6,7,8,10,16,18,44};
    for (int i = 0;i<1000000;i++){
        re = arrayInt(sw,i);
        System.out.println(re);
    }
    System.out.println(System.currentTimeMillis() - start+"毫秒");//12590
}

public static int arrayInt(int[] sw,int z){
    int i = z%10+1;
    if (i>7 || i<1){
        return -1;
    }else {
        return sw[i];
    }
}

以上代码使用全新的思路,使用一个连续的数组代替了 switch 语句。因为对数据的随机访问是非常快的,至少好于 switch 的分支判断。通过实验,使用switch的语句耗时17860ms,使用数组的实现只耗时12590ms,提升了5s多。在软件开发中,换一种思路可能会取得更好的效果,比如使用数组替代switch语句就是就是一个很好的例子。

5、一维数组代替二维数组
由于数组的随机访问的性能非常好,许多JDK类库,如ArrayList、Vector等都是使用了数组作为其数组实现。但是,作为软件开发人员也必须知道,一位数组和二维数组的访问速度是不一样的。一位数组的访问速度要优于二维数组。因此,在性能敏感的系统中要使用二维数组的,可以尝试通过可靠地算法,将二维数组转为一维数组再进行处理,以提高系统的响应速度。

6、提取表达式
在软件开发过程中,程序员很容易有意无意让代码做一些“重复劳动”,在大部分情况下,由于计算机的告诉运行,这些“重复劳动”并不会对性能构成太大的威胁,但若将系统性能发挥到极致,提取这些“重复劳动”相当有意义。

来看下面的测试用例:

@Test
public void test(){
    long start = System.currentTimeMillis();
    ArrayList list = new ArrayList();
    for (int i = 0;i<100000;i++){
        System.out.println(list.add(i));
    }
    //以上是为了做准备
    for (int i = 0;i<list.size();i++){
        System.out.println(list.get(i));
    }
    System.out.println(System.currentTimeMillis() - start);//5444
}

如果我们把list.size()方法提取出来,优化后的代码如下:

@Test
public void test(){
    long start = System.currentTimeMillis();
    ArrayList list = new ArrayList();
    for (int i = 0;i<100000;i++){
        System.out.println(list.add(i));
    }
    //以上是为了做准备
    int n = list.size();
    for (int i = 0;i<n;i++){
        System.out.println(list.get(i));
    }
    System.out.println(System.currentTimeMillis() - start);//3514
}

在我的机器上,前者耗时5444ms,后者耗时3514ms,相差2s左右,可见,提取重复的操作是相当有意义的。

7、展开循环
与前面所介绍的优化技巧略有不同,笔者认为展开循环是一种在极端情况下使用的优化手段,因为展开循环很可能会影响代码的可读性和可维护性,而这两者对软件系统来说也是极为重要的。但是,当性能问题成为系统主要矛盾时,展开循环绝对是一种值得尝试的技术。

8、布尔运算代替位运算
虽然位运算的速度远远高于算术运算,但是在条件判断时,使用位运算替代布尔运算却是非常错误的选择。

在条件判断时,Java会对布尔运算做相当充分的优化。假设有表达式 a,b,c 进行布尔运算“a&&b&&c” ,根据逻辑与的特点,只要在整个布尔表达式中有一项返回false,整个表达式就返回false,因此,当表达式a为false时,该表达式将立即返回 false ,而不会再去计算表达式b 和c。同理,当计算表达式为“a||b||c”时,也是一样。

若使用位运算(按位与”&“、按位或”|“)代替逻辑与和逻辑或,虽然位运算本身没有性能问题,但是位运算总是要将所有的子表达式全部计算完成后,再给出最终结果。因此,从这个角度来说,使用位运算替代布尔运算会使系统进行很多无效计算。

9、使用arrayCopy()
数组复制是一项使用频率很高的功能,JDK中提供了一个高效的API来实现它:

如果在应用程序需要进行数组复制,应该使用这个函数,而不是自己实现。

方法代码:

public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
它的用法是将源数组 src 从索引 srcPos 处复制到目标数组 dest 的 索引destPos处,复制的长度为 length。

System.arraycopy() 方法是 native 方法,通常 native 方法的性能要优于普通的方法。仅出于性能考虑,在软件开发中,尽可能调用 native 方法。

10、使用Buffer进行I/O流操作
除NIO外,使用 Java 进行 I/O操作有两种基本方法:

使用基于InputStream 和 OutputStream 的方式;(字节流)
使用 Writer 和 Reader。(字符流)
无论使用哪种方式进行文件 I/O,如果能合理地使用缓冲,就能有效的提高I/O的性能。

11、使用clone()代替new
在Java中新建对象实例最常用的方法是使用 new 关键字。JDK对 new 的支持非常好,使用 new 关键字创建轻量级对象时,速度非常快。但是,对于重量级对象,由于对象在构造函数中可能会进行一些复杂且耗时的操作,因此,构造函数的执行时间可能会比较长。导致系统短期内无法获得大量的实例。为了解决这个问题,可以使用Object.clone() 方法。

Object.clone() 方法可以绕过构造函数,快速复制一个对象实例。但是,在默认情况下,clone()方法生成的实例只是原对象的浅拷贝。

这里不得不提Java只有值传递了,关于这点,我的理解是基本数据类型引用的是值,普通对象引用的也是值,不过这个普通对象引用的值其实是一个对象的地址。代码示例:

int i = 0;
int j = i; //i的值是0
User user1 = new User();
User user2 = user1; //user1值是new User()的内存地址
如果需要深拷贝,则需要重新实现 clone() 方法。下面看一下ArrayList实现的clone()方法:

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

在ArrayList的clone()方法中,首先使用 super.clone() 方法生成一份浅拷贝对象。然后拷贝一份新的elementData数组让新的ArrayList去引用。使克隆后的ArrayList对象与原对象持有不同的引用,实现了深拷贝。

12、静态方法替代实例方法
使用 static 关键字描述的方法为静态方法。在Java中,由于实例方法需要维护一张类似虚函数表的结构,以实现对多态的支持。与静态方法相比,实例方法的调用需要更多的资源。因此,对于一些常用的工具类方法,没有对其进行重载的必要,那么将它们声明为 static,便可以加速方法的调用。同时,调用 static 方法不需要生成类的实例。比调用实例方法更为方便、易用。

参考
《Java程序性能优化》葛一鸣著

推荐:java性能优化系列集锦

Java性能问题一直困扰着广大程序员,由于平台复杂性,要定位问题,找出其根源确实很难。随着10多年Java平台的改进以及新出现的多核多处理器,Java软件的性能和扩展性已经今非昔比了。现代JVM持续演进,内建了更为成熟的优... 查看详情

有哪些java性能优化方法?

 面试官:“有性能优化经验没?” 应聘者:“有一点。” 面试官:“那你们从哪些方面做了优化?” 应聘者:“sql优化、JVM参数调优、JAVA代码性能优化。。” 面试官: “谈谈编写Java代码时有哪些优化方... 查看详情

java程序性能优化:让你的java程序更快更稳定

...习针对Java程序的优化方法有重要的现实意义。《Java程序性能优化:让你的Java程序更快、更稳定》以Java性能调优为主线,系统地阐述了与Java性能优化相关的知识与技巧。  《Java程序性能优化:让你的Java程序更快、更稳定》... 查看详情

java性能优化

EclipseIDE中设置JVM启动参数https://blog.csdn.net/yangyong0717/article/details/78751008eclipse运行调优(JVM参数配置)https://blog.csdn.net/lhooouuu/article/details/7220721JAVA性能优化:35个小细节让你提升java代码的运行效率https://blog.csdn 查看详情

java性能优化

Java性能优化之编程技巧总结https://www.cnblogs.com/yueshutong/p/9555174.html程序的性能受代码质量的直接影响。在本文中,主要介绍一些代码编写的小技巧和惯例,这些技巧有助于在代码级别上提升系统性能。1、慎用异常在Java软件开发... 查看详情

spark系列(代码片段)

...《Java并发编程》,《Spring核心知识》《Docker教程》和《JVM性能优化》,都是多年面试总结。欢迎关注【后端精进之路】,轻松阅读全部文章。Java并发编程:Java并发编程系列-(1)并发编程基础Java并发编程系列-(2)线程的并发工具类Ja... 查看详情

spark系列(代码片段)

目前已经更新完《Java并发编程》,《JVM性能优化》,《Spring核心知识》《Docker教程》和《Spark基础知识》,都是多年面试总结。欢迎关注【后端精进之路】,轻松阅读全部文章。Java并发编程:Java并发编程系列-(1)并发编程基础Java并... 查看详情

java性能优化(编程随想的博客)

  考虑写性能优化系列,主要是因为之前看到了太多性能其烂无比的Java代码(有些代码看得俺口瞪目呆)。很多Java程序员在写程序时,由于不太了解JVM及语言本身的一些运作机制,从而导致了代码的性能出现【严重】问题(... 查看详情

关于java内存优化和性能优化

...常调用的对象或者变量定义为static,以便提高程序的运行性能。因此,不是常用到的对象或者变量,不要定义为static类型的变量,尤其是静态类对象的定义,一定要仔细考虑是否有必要。例如   类在加载时,静态变量 查看详情

Java HashMap 性能优化/替代

】JavaHashMap性能优化/替代【英文标题】:JavaHashMapperformanceoptimization/alternative【发布时间】:2010-12-1723:02:39【问题描述】:我想创建一个大的HashMap但put()的性能不够好。有什么想法吗?欢迎其他数据结构建议,但我需要JavaMap的查... 查看详情

[java]java程序性能优化

一、避免在循环条件中使用复杂表达式 在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。 例子: importjava.util.vector;classcel{voidmet... 查看详情

java性能优化的七个方向

...1f;本文主要侧重于理论分析,我们从整体上看一下Java性能优化都有哪些可以遵循的规律。本文主讲理论。关于实践,后续的文章会用较多的案例来细化本文的知识点,适合反复思考和归纳。概述性能优化根据优化的... 查看详情

java程序优化(读书笔记)

--From:JAVA程序性能优化(葛一鸣,清华大学出版社,2012/10第一版) 1.java性能调优概述  1.1性能概述     程序性能:执行速度,内存分配,启动时间,负载承受能力。    性能指标:执行时... 查看详情

java性能优化之string字符串优化

...的空间块,因此如何高效地处理字符串,必将是提高整体性能的关键所在。1.字符串对象及其特点Java中八大基本数据类型没有String类型,因为String类型是Java对char数组的进一步封装。String类的实现主要由三部分组成:char数组,off... 查看详情

九大工具助你玩转java性能优化

...章中,我会带着大家一起看一下9个可以帮助我们优化Java性能的工具。有一些我们已经在IDRSolutions中使用了,而另外一些有可能在个人项目中使用。NetBeansProfilerNetBeansprofiler是一个NetBeansIDE插件,主要为NetBeansIDE提供性能分析相关... 查看详情

java回顾之mysql性能优化

java回顾之mysql性能优化一、慢查询日志慢查询日志,可以监控运行效率低下的sql语句,这样就可以知道是哪个sql语句拖累了整体的效率--查看慢查询日志开启情况showvariableslike‘%query%‘;开启慢查询setglobalslow_query_log=on;修改监控sql... 查看详情

90%java程序员被误导的一个性能优化策略(代码片段)

我们经常看到一些Java性能优化的书或者理念,说不要在循环内定义变量,这样会占用过多的内存影响性能,而要在循环外面定义。接触Java这么久以来,相信很多Java程序员都被这种代码性能优化策略所误导。看下面两个示例,示... 查看详情

java性能优化

真正影响JAVA程序性能的,就是碎片化。碎片是JAVA堆内存中的空闲空间,可能是TLAB剩余空间,也可能是被释放掉的具有较长生命周期的小对象占用的空间。减少new对象。每次new对象之后,都要开辟新的内存空间。这些对象不被引... 查看详情