首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【进阶之路】Java代码性能调优(二)

【进阶之路】Java代码性能调优(二)

作者头像
南橘
发布2021-04-02 11:46:53
3240
发布2021-04-02 11:46:53
举报
文章被收录于专栏:进阶之路进阶之路进阶之路

.markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:15px;overflow-x:hidden;color:#333}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{line-height:1.5;margin-top:35px;margin-bottom:10px;padding-bottom:5px}.markdown-body h1{font-size:30px;margin-bottom:5px}.markdown-body h2{padding-bottom:12px;font-size:24px;border-bottom:1px solid #ececec}.markdown-body h3{font-size:18px;padding-bottom:0}.markdown-body h4{font-size:16px}.markdown-body h5{font-size:15px}.markdown-body h6{margin-top:5px}.markdown-body p{line-height:inherit;margin-top:22px;margin-bottom:22px}.markdown-body img{max-width:100%}.markdown-body hr{border:none;border-top:1px solid #ddd;margin-top:32px;margin-bottom:32px}.markdown-body code{word-break:break-word;border-radius:2px;overflow-x:auto;background-color:#fff5f5;color:#ff502c;font-size:.87em;padding:.065em .4em}.markdown-body code,.markdown-body pre{font-family:Menlo,Monaco,Consolas,Courier New,monospace}.markdown-body pre{overflow:auto;position:relative;line-height:1.75}.markdown-body pre>code{font-size:12px;padding:15px 12px;margin:0;word-break:normal;display:block;overflow-x:auto;color:#333;background:#f8f8f8}.markdown-body a{text-decoration:none;color:#0269c8;border-bottom:1px solid #d1e9ff}.markdown-body a:active,.markdown-body a:hover{color:#275b8c}.markdown-body table{display:inline-block!important;font-size:12px;width:auto;max-width:100%;overflow:auto;border:1px solid #f6f6f6}.markdown-body thead{background:#f6f6f6;color:#000;text-align:left}.markdown-body tr:nth-child(2n){background-color:#fcfcfc}.markdown-body td,.markdown-body th{padding:12px 7px;line-height:24px}.markdown-body td{min-width:120px}.markdown-body blockquote{color:#666;padding:1px 23px;margin:22px 0;border-left:4px solid #cbcbcb;background-color:#f8f8f8}.markdown-body blockquote:after{display:block;content:""}.markdown-body blockquote>p{margin:10px 0}.markdown-body ol,.markdown-body ul{padding-left:28px}.markdown-body ol li,.markdown-body ul li{margin-bottom:0;list-style:inherit}.markdown-body ol li .task-list-item,.markdown-body ul li .task-list-item{list-style:none}.markdown-body ol li .task-list-item ol,.markdown-body ol li .task-list-item ul,.markdown-body ul li .task-list-item ol,.markdown-body ul li .task-list-item ul{margin-top:0}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:3px}.markdown-body ol li{padding-left:6px}.markdown-body .contains-task-list{padding-left:0}.markdown-body .task-list-item{list-style:none}@media (max-width:720px){.markdown-body h1{font-size:24px}.markdown-body h2{font-size:20px}.markdown-body h3{font-size:18px}}

导言

大家好,我是南橘,从接触java到现在也有差不多两年时间了,两年时间,从一名连java有几种数据结构都不懂超级小白,到现在懂了一点点的进阶小白,学到了不少的东西。知识越分享越值钱,我这段时间总结(包括从别的大佬那边学习,引用)了一些平常学习和面试中的重点(自我认为),希望给大家带来一些帮助

上一章介绍了字符串、数字和集合类的一些高效用法,这一章就继续查漏补缺、介绍更多的性能优化技巧。

一、int转String的用法

从之前的文章可以得知,int到String的转换是一个耗时的操作,因为我们需要尽量避免做这些转换。如果实在需要,也可以动用上期Integer自动拆包装包的方法,预先将一部分的int值转化为字符串。

我们通过工具类,预先设定好1024个缓存(或者根据业务设置更多),所有调用int2String方法的时候都会预先判断数据是否在缓存内,如果小于1024,则会去调用缓存数据。

测试代码如下,我应该会在下下一章详细介绍我如何通过JMH来对代码性能进行测试

*

大家注意一下这三张图片Benchmark区域的信息不难发现,在数字1024以内,使用缓存的int2StringByCache的性能几乎高出int2String一个数量级。

这里就是运用了JDK对INTEGER自动拆箱装箱的原理

二、使用Native方法

Native方法就是调用一个非Java代码的接口。

我在之前的文章【进阶之路】攻克JVM——JVM的垃圾回收机制(二)里有讲过。

一般来说,作为java的底层代码,Native有着更好的性能。

最常用的Native方法是Stream.arraycopy方法,把原数组的内容复制到目标数组中:

 public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

里面还有各种各样的方法,大家可以根据情况来使用。

三、Switch优化

在条件判断中,如果有较多的分支判断,使用switch语句通常比使用if语句的效率更高。if语句会每次取出变量进行比较从而确定处理分支,而switch语句只需要取出一次变量,然后根据tableswitch直接找到分支即可。

根据测试,分支较少的情况下,if和switch的速度差不多,再分支较多的情况下,switch的速度就快多了。 这里大家可以自己测试一下。

我们知道JDK1.8之后,JDK支持String类型,是因为在变异的时候,使用hashCode来作为switch的实际值。 首先写一段switch结构的代码:

public static void main(String[] args) {
        final String str = "C";

        switch (str) {
            case "A" :
                System.out.println("A");
                break;
            case "B" :
                System.out.println("B");
                break;
            default:
                System.out.println("C");
        }
    }
public static void main(String[] args) {
        String str = "C";
        String var2 = "C";
        byte var3 = -1;
        switch(var2.hashCode()) {
        case 65:
            if (var2.equals("A")) {
                var3 = 0;
            }
            break;
        case 66:
            if (var2.equals("B")) {
                var3 = 1;
            }
        }

        switch(var3) {
        case 0:
            System.out.println("A");
            break;
        case 1:
            System.out.println("B");
            break;
        default:
            System.out.println("C");
        }

}

可以看到,switch结构中变为了String.hashcode()方法,利用其返回的int值进行判断,所以说编译后还是使用了switch(int)结构来实现的。而且我们知道,String的hashcode方法是有哈希冲突的风险的,所以我们应该在每个case条件中增加了equals作为补充判断,避免哈希冲突错误。

四、优先使用局部变量

当存取类变量的时候,Java使用的是虚拟机指令GETFFIELD获取类变量,如果存取方法的变量,则通过出栈操作获取变量。 在之前的文章【进阶之路】攻克JVM——JVM对象及对象的访问定位(一)里有提到

JVM提出栈上分配的概念,针对作用域在方法内的对象,如果满足了逃逸分析,就会将对象属性打散后分配在栈上(线程私有的,属于栈内存),这样,随着方法的调用结束,栈空间的回收就会随着将栈上分配的打散后的对象回收掉,不再给GC增加额外的无用负担,从而提升应用程序整体的性能。

因此在需要频繁操作类变量的时候,最好先赋值给一个局部变量,比如这样:

int[] arr = new int[1024];
        for (int i=0;i<1024;i++){
            arr[i]=i;
        }

但是在实际的开发过程中,由于CPU缓存的原因,并不是每次都从Heap(堆)中取出变量,会从CPU缓存中存取,所以在JMH测试中难以验证谁的性能更好,这边我就不展示了。但是,通过对JVM机制的学习,我们能清楚的知道局部变量的好处。

五、预处理、预分配、预编译

1、预处理

预处理指的是对于需要反复调用的代码,可以尝试取出公共的只读代码块,处理一次生成并保留处理结果。这样接下来需要反复调用的时候,可以直接引用处理的结果。

随便举个例子,假设是黑名单判断,没有做预处理的代码如下:

  ServiceConfig serviceConfig =new ServiceConfig("a,A,S,DA,ASD,F,SDF,SD,F,F,A,D,F");
        Set strings = serviceConfig.getblackService();
         forbid =strings.contains("a");

然后稍微改一下,这里是做了预处理的

public class ServiceConfig{
    String black=null;
    Set blackSet =new HashSet<>();
    public ServiceConfig(String black){
        this.black=black;
        this.blackSet.addAll(Arrays.asList(black.split(",")));
    }
    public Set getblackService(){
        return blackSet;
    }
}

 ServiceConfig serviceConfig =new ServiceConfig("a,A,S,DA,ASD,F,SDF,SD,F,F,A,D,F");
        Set strings = serviceConfig.getblackService();
         forbid =strings.contains("a");

然后放在测试环境一跑,大家看的很明显了,谁才是版本答案:

2、预分配

预分配就很简单了,JDK存在大量预先分配空间的代码,比如我上一章讲的StringBuilder,会初始分配一段空间,不必每次调用append时才分配。

每次在append之前,也会检查分配空间是否足够,如果足够,则不需要增加空间。

如果所有的业务代码都能预分配合理的空间,那么系统的业务性能也会有合理的提高。

3、预编译

JDBC在处理SQL语句时有一个预编译的过程,而预编译对象就是把一些格式固定的SQL编译后,存放在内存池中即JDBC缓冲池,当我们再次执行相同的SQL语句时就不需要预编译的过程了,所以即使SQL注入特殊的语句,也会只当做参数传进去,不会当做指令执行。这个功能一大优势就是能提高执行速度,尤其是多次操作数据库的情况,再一个优势就是预防SQL注入,严格的说,应该是预防绝大多数的SQL注入。

还有种用法,涉及到格式化、序列化的时候,预编译成长红箭格式是一种提高性能的办法,比如在日志输出的时候可能会采用这种方法,

String类的format()方法用于创建格式化的字符串以及连接多个字符串对象,会解析format中出现的“{}”符号

(类似这样)

预编译的方式也能提升服务的性能。

结语

我们在编写代码的过程中,稍稍一注意,就能全面提升代码的性能。这一次的系列文章也是出于这个角度所编写的,接下来我会继续的思考和查阅资料,进一步完善调优系列。 同时需要思维导图的话,可以联系我,毕竟知识越分享越香!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导言
    • 一、int转String的用法
      • 二、使用Native方法
        • 三、Switch优化
          • 四、优先使用局部变量
            • 五、预处理、预分配、预编译
              • 1、预处理
              • 2、预分配
              • 3、预编译
          • 结语
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档