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

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操作有两种基本方法:

  1. 使用基于InputStream 和 OutputStream 的方式;(字节流)
  2. 使用 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性能优化之编程技巧总结的更多相关文章

  1. Java 性能优化的五大技巧

    要对你的 Java 代码进行优化,需要理解 Java 不同要素之间的相互作用,以及它是如何与其运行时的操作系统进行交互的.使用下面这五个技巧和资源,开始学习如何分析和优化你的代码吧. 在我们开始之前, ...

  2. Java 性能优化手册 — 提高 Java 代码性能的各种技巧

    转载: Java 性能优化手册 - 提高 Java 代码性能的各种技巧 Java 6,7,8 中的 String.intern - 字符串池 这篇文章将要讨论 Java 6 中是如何实现 String ...

  3. 《Java性能优化权威指南》

    <Java性能优化权威指南> 基本信息 原书名:Java performance 原出版社: Addison-Wesley Professional 作者: (美)Charlie Hunt ...

  4. 【转】10种简单的Java性能优化

    10种简单的Java性能优化 2015/06/23 | 分类: 基础技术 | 14 条评论 | 标签: 性能优化 分享到: 本文由 ImportNew - 一直在路上 翻译自 jaxenter.欢迎加 ...

  5. JAVA性能优化的五种方式

    一,JAVA性能优化之设计优化 设计优化处于性能优化手段的上层.它往往须要在软件开发之前进行.在软件开发之前,系统架构师应该就评估系统可能存在的各种潜在问题和技术难点,并给出合理的设计方案,因为软件设 ...

  6. 44个Java性能优化

    44个Java性能优化 首先,代码优化的目标是: 减小代码的体积 提高代码运行效率 代码优化细节 1 .尽量指定类.方法的final修饰符 ​ 带有final修饰符的类是不可派生的.在Java核心AP ...

  7. Java 性能优化之 String 篇

    原文:http://www.ibm.com/developerworks/cn/java/j-lo-optmizestring/ Java 性能优化之 String 篇 String 方法用于文本分析 ...

  8. [原创]Java性能优化权威指南读书思维导图

    [原创]Java性能优化权威指南读书思维导图 书名:Java性能优化权威指南 原书名:Java performance 作者: (美)Charlie Hunt    Binu John 译者: 柳飞 ...

  9. 推荐:Java性能优化系列集锦

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

随机推荐

  1. win7文件搜索技巧

    重要说明   (1)搜索的字符串是大小写不敏感的 (2)字符串带双引号与不带双引号是有区别的 如:hello,搜索包含hello单词开头的文件或目录,名为“aa_HELLOcc...”.“cc-Hel ...

  2. 获取Bing每日图片API接口

    bing图片每日更新,对于这一点感觉挺不错的,如果能够把bing每日图片作为博客背景是不是很不错呢?首先我们进入Bing首页,会发现自动转到中国版.不过这没关系,中国版更符合国情,速度也比国际版快一些 ...

  3. Oracle EBS FORM lov

    存在一种情况: 一个LOV的值当前有效,因此填入保存.但突然无效后,当查询该界面时就会弹出LOV框使其修改. 解决方案: 1. 非常粗暴,不设置校验,在LOV对应的item强行将校验设置为NO. 2. ...

  4. Linux系统网络文件配置

    /etc  一.修改配置文档(需要重启网络配置,永远生效) 1.修改IP地址[MariaDB@db1]$ vi /etc/sysconfig/network-scripts/ifcfg-eth0DEV ...

  5. redmine screenshot paste(粘贴截图)

    前言 本文所使用的 redmine 3.1.x 查看redmine版本:以管理员帐号登录 – 点 管理 – 点 信息,示例如下: 在线插件库 插件仓库,可以下载丰富的插件: http://www.re ...

  6. Java中输入字符的排列以及按从小到大的顺序输出

    今天笔试,遇到一个问题,大意就是输入一行字符,例如a b c ,按从小到大的顺序输出它们排列而成的字符串,输出就是abc acb bac bca cba cab.求这个程序怎么实现. 其实这个题很简单 ...

  7. docker容器持久化卷讲解

    docker容器自身存储数据效率比较低,因此我们为了提高磁盘IO的性能等,需要在容器中挂载一个外部存储设备.关于讲解大致如下: Docker中的数据可以存储在类似于虚拟机磁盘的介质中,在Docker中 ...

  8. LeetCode算法题-Rotate Array(Java实现)

    这是悦乐书的第184次更新,第186篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第43题(顺位题号是189).给定一个数组,将数组向右旋转k步,其中k为非负数.例如: ...

  9. hashcode相等两个类一定相等吗?equals呢?相反呢?

    hashCode相等,equals也不一定相等, 两个类也不一定相等 equals相同, 说明是同一个对象, 那么hashCode一定相同 哈希表是结合了直接寻址和链式寻址两种方式,所需要的就是将需要 ...

  10. nginx+keepalived高可用及双主模式

    高可用有2中方式. 1.Nginx+keepalived 主从配置 这种方案,使用一个vip地址,前端使用2台机器,一台做主,一台做备,但同时只有一台机器工作,另一台备份机器在主机器不出现故障的时候, ...