Java中的5个代码性能提升技巧,学会之后立马提升近10倍性能

news2024/11/18 23:21:42

文章目录

    • 预先分配 HashMap 的大小
    • 优化 HashMap 的 key
    • 不使用 Enum.values() 遍历
    • 使用 Enum 代替 String 常量
    • 使用高版本 JDK

这篇文章介绍几个 Java 开发中可以进行 性能优化的小技巧,虽然大多数情况下极致优化代码是没有必要的,但是作为一名技术开发者,我们还是想追求代码的更小、更快,更强。如果哪天你发现程序的运行速度不尽人意,可能会想到这篇文章。

提示:我们不应该为了优化而优化,这有时会增加代码的复杂度。

这篇文章中的代码都在以下环境中进行性能测试。

  • JMH version: 1.33(Java 基准测试框架)
  • VM version: JDK 17, OpenJDK 64-Bit Server VM, 17+35-2724

通过这篇文章的测试,将发现以下几个操作的性能差异。

  1. 预先分配 HashMap 的大小,提高 1/4 的性能。
  2. 优化 HashMap 的 key,性能相差 9.5 倍。
  3. 不使用 Enum.values() 遍历,Spring 也曾如此优化。
  4. 使用 Enum 代替 String 常量,性能高出 1.5 倍。
  5. 使用高版本 JDK,基础操作有 2-5 倍性能差异。

预先分配 HashMap 的大小

HashMap 是 Java 中最为常用的集合之一,大多数的操作速度都非常快,但是 HashMap 在调整自身的容量大小时是很慢且难以自动优化,因此我们在定义一个 HashMap 之前,应该尽可能的给出它的容量大小。给出 size 值时要考虑负载因子,HashMap 默认负载因子是 0.75,也就是要设置的 size 值要除于 0.75。

相关文章:HashMap 源码分析解读

下面使用 JMH 进行基准测试,测试分别向初始容量为 16 和 32 的 HashMap 中插入 14 个元素的效率。

/**



 * @author https://www.wdbyte.com



 */



@State(Scope.Benchmark)



@Warmup(iterations = 3,time = 3)



@Measurement(iterations = 5,time = 3)



public class HashMapSize {



 



    @Param({"14"})



    int keys;



 



    @Param({"16", "32"})



    int size;



 



    @Benchmark



    public HashMap<Integer, Integer> getHashMap() {



        HashMap<Integer, Integer> map = new HashMap<>(size);



        for (int i = 0; i < keys; i++) {



            map.put(i, i);



        }



        return map;



    }



}

HashMap 的初始容量是 16,负责因子 0.75,即最多插入 12 个元素,再插入时就要进行扩容,所以插入 14 个元素过程中需要扩容一次,但是如果 HashMap 初始化时就给了 32 容量,那么最多可以承载 32 * 0.75 = 24 个元素,所以插入 14 个元素时是不需要扩容操作的。

# JMH version: 1.33



# VM version: JDK 17, OpenJDK 64-Bit Server VM, 17+35-2724



 



Benchmark               (keys)  (size)   Mode  Cnt        Score        Error  Units



HashMapSize.getHashMap      14      16  thrpt   25  4825825.152 ± 323910.557  ops/s



HashMapSize.getHashMap      14      32  thrpt   25  6556184.664 ± 711657.679  ops/s

可以看到在这次测试中,初始容量为32 的 HashMap 比初始容量为 16 的 HashMap 每秒可以多操作 26% 次,已经有 1/4 的性能差异了。

优化 HashMap 的 key

如果 HashMap 的 key 值需要用到多个 String 字符串时,把字符串作为某个类属性,然后使用这个类的实例作为 key 会比使用字符串拼接效率更高。

下面测试使用两个字符串拼接作为 key,和把两个字符串作为 MutablePair 类的属性引用,然后使用 MutablePair 对象作为 key 的运行效率差异。

/**



 * @author https://www.wdbyte.com



 */



@State(Scope.Benchmark)



@Warmup(iterations = 3, time = 3)



@Measurement(iterations = 5, time = 3)



public class HashMapKey {



 



    private int size = 1024;



    private Map<String, Object> stringMap;



    private Map<Pair, Object> pairMap;



    private String[] prefixes;



    private String[] suffixes;



 



    @Setup(Level.Trial)



    public void setup() {



        prefixes = new String[size];



        suffixes = new String[size];



        stringMap = new HashMap<>();



        pairMap = new HashMap<>();



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



            prefixes[i] = UUID.randomUUID().toString();



            suffixes[i] = UUID.randomUUID().toString();



            stringMap.put(prefixes[i] + ";" + suffixes[i], i);



            // use new String to avoid reference equality speeding up the equals calls



            pairMap.put(new MutablePair(prefixes[i], suffixes[i]), i);



        }



    }



 



    @Benchmark



    @OperationsPerInvocation(1024)



    public void stringKey(Blackhole bh) {



        for (int i = 0; i < prefixes.length; i++) {



            bh.consume(stringMap.get(prefixes[i] + ";" + suffixes[i]));



        }



    }



 



    @Benchmark



    @OperationsPerInvocation(1024)



    public void pairMap(Blackhole bh) {



        for (int i = 0; i < prefixes.length; i++) {



            bh.consume(pairMap.get(new MutablePair(prefixes[i], suffixes[i])));



        }



    }



}

测试结果:

# JMH version: 1.33



# VM version: JDK 17, OpenJDK 64-Bit Server VM, 17+35-2724



 



Benchmark              Mode  Cnt         Score         Error  Units



HashMapKey.pairMap    thrpt   25  89295035.436 ± 6498403.173  ops/s



HashMapKey.stringKey  thrpt   25   9410641.728 ±  389850.653  ops/s

可以发现使用对象引用作为 key 的性能,是使用 String 拼接作为 key 的性能的 9.5 倍

不使用 Enum.values() 遍历

我们通常会使用 Enum.values() 进行枚举类遍历,但是这样每次调用都会分配枚举类值数量大小的数组用于操作,这里完全可以缓存起来,以减少每次内存分配的时间和空间消耗。

/**



 * 枚举类遍历测试



 *



 * @author https://www.wdbyte.com



 */



@State(Scope.Benchmark)



@Warmup(iterations = 3, time = 3)



@Measurement(iterations = 5, time = 3)



@BenchmarkMode(Mode.AverageTime)



@OutputTimeUnit(TimeUnit.MILLISECONDS)



public class EnumIteration {



    enum FourteenEnum {



        a,b,c,d,e,f,g,h,i,j,k,l,m,n;



 



        static final FourteenEnum[] VALUES;



        static {



            VALUES = values();



        }



    }



 



    @Benchmark



    public void valuesEnum(Blackhole bh) {



        for (FourteenEnum value : FourteenEnum.values()) {



            bh.consume(value.ordinal());



        }



    }



 



    @Benchmark



    public void enumSetEnum(Blackhole bh) {



        for (FourteenEnum value : EnumSet.allOf(FourteenEnum.class)) {



            bh.consume(value.ordinal());



        }



    }



 



    @Benchmark



    public void cacheEnums(Blackhole bh) {



        for (FourteenEnum value : FourteenEnum.VALUES) {



            bh.consume(value.ordinal());



        }



    }



}

运行结果

# JMH version: 1.33



# VM version: JDK 17, OpenJDK 64-Bit Server VM, 17+35-2724



 



Benchmark                   Mode  Cnt         Score         Error  Units



EnumIteration.cacheEnums   thrpt   25  15623401.567 ± 2274962.772  ops/s



EnumIteration.enumSetEnum  thrpt   25   8597188.662 ±  610632.249  ops/s



EnumIteration.valuesEnum   thrpt   25  14713941.570 ±  728955.826  ops/s

很明显使用缓存后的遍历速度是最快的,使用 EnumSet 遍历效率是最低的,这很好理解,数组的遍历效率是大于哈希表的。

可能你会觉得这里使用 values() 缓存和直接使用 Enum.values() 的效率差异很小,其实在某些调用频率很高的场景下是有很大区别的,在 Spring 框架中,曾使用 Enum.values() 这种方式在每次响应时遍历 HTTP 状态码枚举类,这在请求量大时造成了不必要的性能开销,后来进行了 values() 缓存优化。

下面是这次提交的截图:

img

Spring Enum.values 改动

使用 Enum 代替 String 常量

使用 Enum 枚举类代替 String 常量有明显的好处,枚举类强制验证,不会出错,同时使用枚举类的效率也更高。即使作为 Map 的 key 值来看,虽然 HashMap 的速度已经很快了,但是使用 EnumMap 的速度可以更快。

提示:不要为了优化而优化,这会增加代码的复杂度。

下面测试使用使用 Enum 作为 key,和使用 String 作为 key,在 map.get 操作下的性能差异。

/**



 * @author https://www.wdbyte.com



 */



@State(Scope.Benchmark)



@Warmup(iterations = 3, time = 3)



@Measurement(iterations = 5, time = 3)



public class EnumMapBenchmark {



 



    enum AnEnum {



        a, b, c, d, e, f, g,



        h, i, j, k, l, m, n,



        o, p, q,    r, s, t,



        u, v, w,    x, y, z;



    }



 



    /** 要查找的 key 的数量 */



    private static int size = 10000;



    /** 随机数种子 */



    private static int seed = 99;



 



    @State(Scope.Benchmark)



    public static class EnumMapState {



        private EnumMap<AnEnum, String> map;



        private AnEnum[] values;



 



        @Setup(Level.Trial)



        public void setup() {



            map = new EnumMap<>(AnEnum.class);



            values = new AnEnum[size];



            AnEnum[] enumValues = AnEnum.values();



            SplittableRandom random = new SplittableRandom(seed);



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



                int nextInt = random.nextInt(0, Integer.MAX_VALUE);



                values[i] = enumValues[nextInt % enumValues.length];



            }



            for (AnEnum value : enumValues) {



                map.put(value, UUID.randomUUID().toString());



            }



        }



    }



 



    @State(Scope.Benchmark)



    public static class HashMapState{



        private HashMap<String, String> map;



        private String[] values;



 



        @Setup(Level.Trial)



        public void setup() {



            map = new HashMap<>();



            values = new String[size];



            AnEnum[] enumValues = AnEnum.values();



            int pos = 0;



            SplittableRandom random = new SplittableRandom(seed);



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



                int nextInt = random.nextInt(0, Integer.MAX_VALUE);



                values[i] = enumValues[nextInt % enumValues.length].toString();



            }



            for (AnEnum value : enumValues) {



                map.put(value.toString(), UUID.randomUUID().toString());



            }



        }



    }



 



    @Benchmark



    public void enumMap(EnumMapState state, Blackhole bh) {



        for (AnEnum value : state.values) {



            bh.consume(state.map.get(value));



        }



    }



 



    @Benchmark



    public void hashMap(HashMapState state, Blackhole bh) {



        for (String value : state.values) {



            bh.consume(state.map.get(value));



        }



    }



}

运行结果:

# JMH version: 1.33



# VM version: JDK 17, OpenJDK 64-Bit Server VM, 17+35-2724



 



Benchmark                  Mode  Cnt      Score      Error  Units



EnumMapBenchmark.enumMap  thrpt   25  22159.232 ± 1268.800  ops/s



EnumMapBenchmark.hashMap  thrpt   25  14528.555 ± 1323.610  ops/s

很明显,使用 Enum 作为 key 的性能比使用 String 作为 key 的性能高出 1.5 倍。但是仍然要根据实际情况考虑是否使用 EnumMap 和 EnumSet。

使用高版本 JDK

String 类应该是 Java 中使用频率最高的类了,但是 Java 8 中的 String 实现相比高版本 JDK ,则占用空间更多,性能更低。

下面测试 String 转 bytes 和 bytes 转 String 在 Java 8 以及 Java 11 中的性能开销。

/**



 * @author https://www.wdbyte.com



 * @date 2021/12/23



 */



@State(Scope.Benchmark)



@Warmup(iterations = 3, time = 3)



@Measurement(iterations = 5, time = 3)



public class StringInJdk {



 



    @Param({"10000"})



    private int size;



    private String[] stringArray;



    private List<byte[]> byteList;



 



    @Setup(Level.Trial)



    public void setup() {



        byteList = new ArrayList<>(size);



        stringArray = new String[size];



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



            String uuid = UUID.randomUUID().toString();



            stringArray[i] = uuid;



            byteList.add(uuid.getBytes(StandardCharsets.UTF_8));



        }



    }



 



    @Benchmark



    public void byteToString(Blackhole bh) {



        for (byte[] bytes : byteList) {



            bh.consume(new String(bytes, StandardCharsets.UTF_8));



        }



    }



 



    @Benchmark



    public void stringToByte(Blackhole bh) {



        for (String s : stringArray) {



            bh.consume(s.getBytes(StandardCharsets.UTF_8));



        }



    }



}

测试结果:

# JMH version: 1.33



# VM version: JDK 1.8.0_151, Java HotSpot(TM) 64-Bit Server VM, 25.151-b12



 



Benchmark                 (size)   Mode  Cnt     Score     Error  Units



StringInJdk.byteToString   10000  thrpt   25  2396.713 ± 133.500  ops/s



StringInJdk.stringToByte   10000  thrpt   25  1745.060 ±  16.945  ops/s



 



# JMH version: 1.33



# VM version: JDK 17, OpenJDK 64-Bit Server VM, 17+35-2724



 



Benchmark                 (size)   Mode  Cnt     Score     Error  Units



StringInJdk.byteToString   10000  thrpt   25  5711.954 ±  41.865  ops/s



StringInJdk.stringToByte   10000  thrpt   25  8595.895 ± 704.004  ops/s

可以看到在 bytes 转 String 操作上,Java 17 的性能是 Java 8 的 2.5 倍左右,而 String 转 bytes 操作,Java 17 的性能是 Java 8 的 5 倍。关于字符串的操作非常基础,随处可见,可见高版本的优势十分明显。

一如既往,当前文章中的代码示例都存放在 github.com/niumoo/JavaNotes.

参考

https://richardstartin.github.io/posts/5-java-mundane-performance-tricks

https://github.com/spring-projects/spring-framework/issues/26842

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

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

相关文章

如何通过nodejs快速搭建一个服务器

在前端开发过程中&#xff0c;可能某些时候需要自己搭建一台服务器用于一些文件图片请求或者进行后端相关知识的学习。本文主要讲解如何通过nodejs进行一个基础服务器的搭建&#xff0c;包括如何将文件布置的服务器&#xff0c;以及基础接口的开发。后面可能会更新关于通过node…

C# SuperSocket 手把手教你入门 傻瓜教程---9(CountSpliterReceiveFilter - 固定数量分隔符协议)使用COMMAND

C# SuperSocket 手把手教你入门 傻瓜教程系列教程 C# SuperSocket 手把手教你入门 傻瓜教程---1&#xff08;服务器单向接收客户端发送数据&#xff09; C# SuperSocket 手把手教你入门 傻瓜教程---2&#xff08;服务器和客户端双向通信&#xff09; C# SuperSocket 手把手教…

【Vue 快速入门系列】Vue数据实现本地存储、自定义事件绑定、全局事件总线、$nextTick的使用

文章目录一、本地存储1.概念2.接口3.实际操作二、自定义事件绑定1.自定义事件使用过程2.绑定自定义事件的语法3.其他注意事项三、全局事件总线1.全局事件总线概念2.全局事件总线的使用方式3.原理剖析四、$nextTick小技巧一、本地存储 1.概念 可以将数据临时存储到本地浏览器&…

基于适应度-距离平衡的人工生态系统优化求解暂态稳定约束最优潮流问题附matlab代码

​✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法…

数字时代,企业应该如何看待商业智能BI

新一代数字化相关技术的应用&#xff0c;使得数字化产品和服务覆盖了社会的方方面面&#xff0c;也让数据成为了构建现代化社会的核心元素&#xff0c;让人们明白数据不只是人类活动产生的附加品&#xff0c;还能够在应用过程中促进人类活动发展、优化和改变&#xff0c;真正成…

Python时间模块之datetime模块

目录 简介 函数介绍及运用 date&#xff1a;日期类 1.获取当前时间 2.日期对象的属性 3.date类中时间和时间戳的转换&#xff1a; 4.修改日期使用replace方法 time&#xff1a;时间类 time类操作 datetime&#xff1a;日期时间类 timedelta&#xff1a;时间间隔&…

【MySQL常用性能指标】

这里给大家分享一些MySQL的常用性能指标&#xff0c;可以对此增加一些自定义指标到数据库的监控里&#xff0c;如zabbix或者prometheus&#xff0c;来更好的检测数据库的状态。 我的MySQSL版本是5.7.19。因为是自己的测试环境&#xff0c;所以截图的一些指标很低&#xff0c;仅…

[附源码]Python计算机毕业设计SSM基于的考研信息共享平台(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

linux下故障硬盘点灯操作

按照常理说&#xff0c;硬盘故障了&#xff0c;会自动亮红灯&#xff0c;故障消除后会自动恢复正常&#xff0c;这个本来是服务器自带的功能。但现实情况往往不是这样&#xff0c;即使硬盘故障了&#xff0c;现场通过外观也看不出来。 1 硬盘点灯的意义 在运维的工作中&#…

【电巢】什么是EMC测试?EMC测试又是如何分类的?

在CE认证里面针对不同产品有不同的测试标准、指令&#xff0c;带电产品中必须要测是的EMC指令&#xff0c;作为最常见的CE指令&#xff0c;很多客户想知道什么是EMC测试? EMC的基本定义 EDA365电子论坛 EMC是评价产品质量的一个重要指标。 1.电磁兼容EMC (Electro-Magnetic …

KVM虚拟化

概述 虚拟化是一种技术&#xff0c;可以利用以往局限于硬件的资源来创建有用的 IT 服务。它让您能够将物理计算机的工作能力分配给多个用户或环境&#xff0c;从而充分利用计算机的所有能力。 1.1 工作原理 一种叫做 Hypervisor &#xff08;虚拟机监控程序&#xff09;的软件…

非零基础自学计算机操作系统 第1章 操作系统概述 1.6 操作系统的界面形式 1.7 操作系统的运行机理

非零基础自学计算机操作系统 文章目录非零基础自学计算机操作系统第1章 操作系统概述1.6 操作系统的界面形式1.6.1 交互终端命令1.6.2 图形用户界面1.6.3 触屏用户界面1.6.4 作业控制语言1.6.5 系统调用命令1.7 操作系统的运行机理第1章 操作系统概述 1.6 操作系统的界面形式 …

mac回收站清空还能恢复吗?苹果电脑删除的文件怎么恢复

mac回收站清空还能恢复吗&#xff1f;通常&#xff0c;我们右键从电脑上删除的文件&#xff0c;都是被保存在回收站的&#xff0c;那么从回收站删除的文件去哪儿了呢&#xff1f; 当文件从回收站删除后&#xff0c;这些文件还保留在电脑系统上面&#xff0c;只是我们没有办法看…

Java性能调优System的gc垃圾回收方法

java性能调优System的gc垃圾回收方法 java性能调优System的gc垃圾回收方法示例解 一、什么是System.gc()&#xff1f; System.gc()是用Java&#xff0c;C&#xff03;和许多其他流行的高级编程语言提供的API。当它被调用时&#xff0c;它将尽最大努力从内存中清除垃圾&#…

ABHD5 调控细胞自噬依赖的嘧啶合成介导结肠癌对5-FU 的药物敏感性改变

​ 发表期刊&#xff1a;Nature Communications 影响因子&#xff1a;12.353 发表时间&#xff1a;2019年 合作单位&#xff1a;第三军医大学附属西南医院 今天百趣代谢组学将给大家分享Nature Communications上的一篇文章&#xff1a;ABHD5 blunts the sensitivity of col…

这 88 道阿里高级岗面试题,刷掉了 80% 以上的 Java 程序员

2022&#xff0c;可谓是招聘面试最难季。不少大厂&#xff0c;如腾讯、字节的招聘名额明显减少&#xff0c;面试门槛却一再拔高&#xff0c;如果不用心准备&#xff0c;很可能就被面试官怼得哑口无言。今天不谈其它&#xff0c;就说说我作为面试官面试的那些事儿。 从某电商项目…

JVM监控及诊断工具之命令行篇

文章目录1. 概述2. jps&#xff1a;查看正在运行的Java进程3. jstat&#xff1a;查看JVM统计信息4. jinfo&#xff1a;实时查看和修改JVM配置参数5. jmap&#xff1a;导出内存映像文件&内存使用情况6、jhat&#xff1a;JDK自带堆分析工具7 jstack&#xff1a;打印JVM中线程…

基于微信小程序的旅游系统-计算机毕业设计

项目介绍 随着人民生活水平的提高,旅游业已经越来越大众化,而旅游业的核心是信息,不论是对旅游管理部门、对旅游企业,或是对旅游者而言,有效的获取旅游信息,都显得特别重要.自助定制游将使旅游相关信息管理工作规范化、信息化、程序化,提供旅游景点、旅游线路,旅游新闻等服务本…

_5LeetCode代码随想录算法训练营第五天-C++哈希表

_5LeetCode代码随想录算法训练营第五天-C哈希表 LeetCode 242.有效的字母异位词LeetCode 349.两个数组的交集LeetCode 202.快乐数LeetCode 1.两数之和 本文截图参考代码随想录&#xff1a;https://programmercarl.com/ 哈希表 定义 哈希表是根据键值而直接进行访问的数据结…

shell 创建子进程及并行延时执行命令方法

shell 创建子进程方法 1. 什么是shell子进程 子进程&#xff0c;是从父子进程的概念出发的&#xff0c;unix操作系统的进程从init进程开始&#xff08;init进程为1,而进程号0为系统原始进程&#xff0c;以下讨论的进程原则上不包括进程0)均有其对应的子进程&#xff0c;就算是…