Java程序性能优化技巧

news2024/12/27 15:22:49

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<<=1a>>=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 方法不需要生成类的实例。比调用实例方法更为方便、易用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/680919.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从零开始 verilog 以太网交换机(五)帧合路单元的设计与实现

从零开始 verilog 以太网交换机&#xff08;五&#xff09;帧合路单元的设计与实现 &#x1f508;声明&#xff1a; &#x1f603;博主主页&#xff1a;王_嘻嘻的CSDN主页 &#x1f9e8; 从零开始 verilog 以太网交换机系列专栏&#xff1a;点击这里 &#x1f511;未经作者允许…

07-歌词滚动效果

现在学习的代码工作中不一定会需要&#xff0c;如果有&#xff0c;也已经做成了产品和库&#xff0c; 前端重点是创造&#xff0c;面试官考验你的能力是会提出最刁钻的问题给你的。 项目效果 原生JS效率是最高的&#xff0c;框架只是提高代码的可读性 favico图标添加 两种方…

一个工具类让你彻底解决bean深拷贝

深拷贝是我们在代码开发当中经常需要使用到的&#xff0c;但是市面上的对象拷贝方法&#xff0c;比如spring自带的&#xff0c;或者其他工具类带的对象拷贝&#xff0c;大部分都是浅拷贝&#xff0c;根本无法满足咱们的业务需求&#xff0c;我们就只能对里面的引用对象进行专门…

guacamole 纯web rdp预研:web应用程序部分

文章目录 web rdp预研 web应用程序部分预研目的相关基础Web应用结构&#xff08;框架&#xff09;配置tomcat运行web项目与前端交互原理问题整理Java web基础知识Java web调试预研结论 web rdp预研 web应用程序部分 ⭐️来自很多年前的笔记&#xff0c;只是一个归档&#xff0…

面向对象【成员变量与局部变量、方法声明与作用】

文章目录 成员变量局部变量成员变量与局部变量的区别 方法方法的作用方法的声明 成员变量 Java中的成员变量是指类中声明的变量&#xff0c;也称为实例变量或属性。它们与方法一样属于类的成员&#xff0c;不同之处在于&#xff0c;它们存储在对象(堆)中而不是栈中&#xff0c;…

基于灰色预测模型的负荷预测(matlab程序)

0.代码链接 基于灰色预测模型的负荷预测&#xff08;matlab程序&#xff09;资源-CSDN文库 1.简述 灰色预测是一种对含有不确定因素的系统进行预测的方法。灰色预测通过鉴别系统因素之间发展趋势的相异程度&#xff0c;即进行关联分析&#xff0c;并对原始数据进行生成处理来…

python:并发编程(二十六)

前言 本文将和大家一起探讨python并发编程的实际项目&#xff1a;win图形界面应用&#xff08;篇八&#xff0c;共八篇&#xff09;&#xff0c;系列文章将会从零开始构建项目&#xff0c;并逐渐完善项目&#xff0c;最终将项目打造成适用于高并发场景的应用。 本文为python并…

ADAS(高级驾驶员辅助系统)

什么是 ADAS ADAS —— 高级驾驶员辅助系统&#xff0c;包含一系列硬件和软件组件&#xff0c;自动起到驾驶员的多项作用。目前&#xff0c;常见的车辆 ADAS 功能包括自适应巡航控制、盲点检测、变道检测(车道偏离警告系统)、自动车道跟随和自动紧急制动、泊车。 L0到L2称为AD…

测试用例详解(强,硬,牛)

目录&#xff1a; 测试用例价值与体系黑盒测试方法论-等价类黑盒测试方法论-边界值黑盒测试方法论-因果图黑盒测试方法论-判定表黑盒测试方法论-场景法测试用例基础概念测试用例设计与评审面试测试测试用例设计搜索功能测试用例设计 1.测试用例价值与体系 测试用例概念测试…

【MQTT】| 搭建——在云服务器上搭建MQTT服务器

系列文章目录 【MQTT】| 搭建——在云服务器上搭建MQTT服务 失败了也挺可爱&#xff0c;成功了就超帅。 文章目录 前言1. EMQX简介2. EMQX部署3. EMQX一些操作指令3.1 启动EMQX3.2 停止EMQX3.3 检查EMQX运行状态3.4 卸载EMQX 4. EMQX设置4.1 进入EMQX控制面板 5. EMQX测试5.1…

模型评估 (Model Assessment)

1.模型评估 (Model Assessment) 笔记来源于《白话机器学习的数学》 我们训练好模型后&#xff0c;要对知道这个模型到底好不好&#xff0c;定量描述这个模型好坏就是模型评估 把获取的全部训练数据分成两份&#xff1a;一份用于测试&#xff0c;一份用于训练。然后用前者来评估…

数据库实训报告3000字

数据库实训报告1 一、实习目的 认识实习是本科教学计划中非常重要的实践性教学环节&#xff0c;其目的是使学生了解和掌握电力生产知识、印证、巩固和丰富已学过的计算机专业课程内容&#xff0c;培养学生理论联系实际&#xff0c;提高其在生产实践中调查研究、观察问题、分析问…

银行账户管理系统

1. 目的与要求 1、目的: (1)熟练掌握 C语言的基本知识和技能: (2)掌握面向对象程序设计的基本思想和方法;(3)能够利用所学的面向对象基本知识和技能&#xff0c;解决简单应用的程序设计 2、基本要求:(1)利用面向对象的方法以及 C的编程思想来完成系统的设计;(2)在设计的过程中…

抽象工厂模式(Abstract Factory)

定义 抽象工厂是一种创建型设计模式&#xff0c;它能创建一系列相关的对象&#xff0c;而无需指定其具体类。 前言 1. 问题 假设你正在开发一款家具商店模拟器。你的代码中包括一些类&#xff0c;用于表示&#xff1a; 一系列相关产品&#xff0c;例如椅子&#xff08;Chai…

091基于深度学习的手写汉字数字识别含10多种模型

emo仓库和视频演示找091期&#xff1a; 银色子弹zg的个人空间-银色子弹zg个人主页-哔哩哔哩视频 效果展示图如下&#xff1a; 代码文件展示如下&#xff1a; 运行01数据集文本生成制作.py可以读取图片路径保存再txt文本中&#xff0c; 运行02train.py可以对txt文本中的图片路…

同程数科基于 Apache Doris 构建统一实时数仓,查询提速数十倍!

本文导读&#xff1a; 同程数科是同程集团旗下的旅游产业金融科技服务平台&#xff0c;为上下游企业和个人消费者提供数字金融科技服务。近年来&#xff0c;随着同程数科业务的不断拓展和用户量的增加&#xff0c;高效可靠的一站式数据中心建设已成为必不可少的需求。为帮助业…

团体程序设计天梯赛-练习集L2篇④

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的普通人。 &#x1f3c0;个人主页&#xff1a;陈童学哦CSDN &#x1f4a1;所属专栏&#xff1a;PTA &#x1f381;希望各…

WPF 零基础入门笔记(1):WPF静态页面,布局+样式+触发器

文章目录 官方文档往期回顾零基础笔记项目实战&#xff08;已完结&#xff09; WPF项目创建为什么选net core版本 WPF 静态页面WPF 页面布局WPF样式Style样式行内样式行外样式如果是简单样式&#xff0c;可以这么写如果是复杂样式 WPF样式继承WPF触发器单条件触发器多条件触发 …

LLDP(链路层发现协议)详解及C/C++代码实现

LLDP&#xff08;链路层发现协议&#xff09;是一种IEEE标准协议&#xff08;IEEE 802.1AB&#xff09;&#xff0c;它定义了封装在以太网帧中的消息&#xff0c;目的是通过默认情况下每30秒从每个端口定期重传一次&#xff0c;为设备提供一种向LAN&#xff08;局域网&#xff…

20个Java编程技巧

1. 把字符串常量放在前面 通过把字符串常量放在比较函数equals()比较项的左侧来防止偶然的 NullPointerException 从来都不是一个坏主意&#xff0c;就像这样&#xff1a; 这是毫无疑问的&#xff0c;把一种表达式转换成另一种更好的表达式&#xff0c;并不会失去什么。只要我…