Java代码性能优化总结

Wesley13
• 阅读 553

前言

代码优化,一个很重要的课题。可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用,但是,吃的小虾米一多之后,鲸鱼就被喂饱了。代码优化也是一样,如果项目着眼于尽快无BUG上线,那么此时可以抓大放小,代码的细节可以不精打细磨;但是如果有足够的时间开发、维护代码,这时候就必须考虑每个可以优化的细节了,一个一个细小的优化点累积起来,对于代码的运行效率绝对是有提升的。

代码优化的目标

1、减小代码的体积

2、提高代码运行的效率

3、提高代码的可读性

代码优化细节

1、字符串变量和字符串常量equals的时候将字符串常量写在前面

这是一个比较常见的小技巧了,如果有以下代码:

String str = "123";
if (str.equals("123")) {
...
}
建议修改为:
String str = "123";
if ("123".equals(str))
{
...
}

这么做主要是可以避免空指针异常。

2、把一个基本数据类型转为字符串,基本数据类型.toString()是最快的方式、String.valueOf(数据)次之、数据+”"最慢

把一个基本数据类型转为一般有三种方式,我有一个Integer型数据i,可以使用i.toString()、String.valueOf(i)、i+”"三种方式,三种方式的效率如何,看一个测试:

public static void main(String[] args)
{ 
int loopTime = 50000;
Integer i = 0; long startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++)
{
String str = String.valueOf(i);
}
System.out.println("String.valueOf():" + (System.currentTimeMillis() - startTime) + "ms");
startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++)
{
String str = i.toString();
}
System.out.println("Integer.toString():" + (System.currentTimeMillis() - startTime) + "ms");
startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++)
{
String str = i + "";
}
System.out.println("i + \"\":" + (System.currentTimeMillis() - startTime) + "ms");
}

运行结果为:

String.valueOf():11ms Integer.toString():5ms i + "":25ms

所以以后遇到把一个基本数据类型转为String的时候,优先考虑使用toString()方法。至于为什么,很简单:

a、String.valueOf()方法底层调用了Integer.toString()方法,但是会在调用前做空判断

b、Integer.toString()方法就不说了,直接调用了

c、i + “”底层使用了StringBuilder实现,先用append方法拼接,再用toString()方法获取字符串

三者对比下来,明显是2最快、1次之、3最慢。

3、尽量减少对变量的重复计算

明确一个概念,对方法的调用,即使方法中只有一句语句,也是有消耗的,包括创建栈帧、调用方法时保护现场、调用方法完毕时恢复现场等。所以例如下面的操作:

for (int i = 0; i < list.size(); i++)
{...}

建议替换为:

for (int i = 0, int length = list.size(); i < length; i++)
{...}

这样,在list.size()很大的时候,就减少了很多的消耗。

4、设计API时永远不要返回空(null)数组或List

public static void main(String[] args){
    int limit =1;
    List<String> ls = getUsers(limit);
    for(String str:ls){
        System.out.println(str);
    }
}

private static List<String> getUsers(){
     //return null;直接返回null,在上面的遍历会有异常java.lang.NullPointerException
    return Collection.EMPTY_LIST;  
}

Java 的标准库设计者已经在 Collections 类中放了一个空的 List 常量 EMPTY_LIST,除此之外,还有 EMPTY_MAP, EMPTY_SET,真是贴心。

5、将字符串数组转换成逗号分隔字符串

通常会这么写:

public static void main(String[] args) {
String strs = "";
String[] arr = new String[]{"aa", "cc", "bb"}; // 转换前的字符串数组
StringBuilder sb = new StringBuilder();
for (String ele : arr) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(ele);
}
strs = sb.toString(); // 转换后的逗号分隔字符串
System.out.println(strs);//aa,cc,bb
}

更简单的写法:

public static void main(String[] args) {
String[] arr = new String[]{"aa", "cc", "bb"}; // 转换前的字符串数组
String strs = String.join(",", arr); // 转换后的逗号分隔字符串
System.out.println(strs);//aa,cc,bb
}

6、三元表达式代替if else

if(user!=null)
{
  return true;
}else
{
  return false;
}

建议替换为:

return user==null?false:true;

7、不可随意使用静态变量

当某个对象被定义为 static 变量,那么 GC 通常是不会回收这个对象所占有的内存。

示例如下:

public class A {
  private static B b = new B();
}

此时静态变量 b 的生命周期与 A 类同步,即如果 A 类不卸载,b 对象会常驻内存,直到程序终止。

 8、尽量采用懒加载的策略,即在需要的时候才创建

例如:

String str = "aaa";if (i == 1)
{
list.add(str);
}
建议替换为:

if (i == 1)
{
String str = "aaa";
list.add(str);
}

 9、慎用异常

在Java开发中,经常使用try-catch进行错误捕获,但是try-catch语句对系统性能而言是非常糟糕的。虽然一次try-catch中,无法察觉到她对性能带来的损失,但是一旦try-catch语句被应用于循环或是遍历体内,就会给系统性能带来极大的伤害。 以下是一段将try-catch应用于循环体内的示例代码:

@Test
    public void test11() {

        long start = System.currentTimeMillis();
        int a = 0;
        for(int i=0;i<1000000000;i++){
            try {
                a++;
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        long useTime = System.currentTimeMillis()-start;
        System.out.println("useTime:"+useTime);

    }

上面这段代码运行结果是:useTime:10

下面是一段将try-catch移到循环体外的代码,那么性能就提升了将近一半。如下:

@Test
    public void test(){
        long start = System.currentTimeMillis();
        int a = 0;
        try {
            for (int i=0;i<1000000000;i++){
                a++;
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        long useTime = System.currentTimeMillis()-start;
        System.out.println(useTime);
    }

useTime:6

10、使用HashMap的时候,可以指定集合的初始化大小。

例如说,HashMap里面需要存放30个元素,但是由于没有进行初始化大小操作,所以在添加元素的时候,当0.75 * size(默认16) < 30.hashmap的内部会一直在进行扩容操作,影响性能。

那么为了减少扩容操作,可以在初始化的时候将hashmap的大小设置为:已知需要存储的大小/负载因子(0.75)+1

Map<String, Object> params = new HashMap<>(41);

PS:如果能估计到待添加的内容长度,为底层以数组方式实现的集合、工具类指定初始长度:ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等

11、MyBatis中Integer型的字段 0=='' 为true 的情况确实存在,所以对于Integer型的数据应该避免 !='' 的判断。

<if test="cardType != null and cardType !='' ">
    and card_type = #{cardType,jdbcType=INTEGER}
</if>

改成

<if test="cardType != null">
    and card_type = #{cardType,jdbcType=INTEGER}
</if>

因为当cardType=0时,cardType !=''为false,所以INGEGER类型要去掉对空串的判断。

12、StringUtils工具类中:isNotEmpty与isNotBlank

1,isNotEmpty(str)等价于 str != null && str.length > 0。

2,isNotBlank(str) 等价于 str != null && str.length > 0 && str.trim().length > 0。

同理:

1,isEmpty 等价于 str == null || str.length == 0。

2,isBlank 等价于 str == null || str.length == 0 || str.trim().length == 0。

3,str.length > 0 && str.trim().length > 0 ---> str.length > 0。

 13、及时关闭流

FileInputStream fileInputStream = null;
try {
    File file = cn.hutool.core.io.FileUtil.file(filePath);
    fileInputStream = new FileInputStream(file);
    .....
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        if (fileInputStream != null) {
            fileInputStream.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Java编程过程中,进行数据库连接、I/O流操作时务必小心,在使用完毕后,及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销,稍有不慎,将会导致严重的后果。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
java 性能优化:35 个小细节,让你提升 java 代码的运行效率
前言代码 优化 ,一个很重要的课题。可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用,但是,吃的小虾米一多之后,鲸鱼就被喂饱了。代码优化也是一样,如果项目着眼于尽快无BUG上线,那么此时可以抓大放小,代码的细节可以不精打细磨;但
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这