点击上方 果汁简历 ,选择“置顶公众号”

优质文章,第一时间送达

文章目录

  1. String.replace vs StringUtils.replace
  2. 什么是 JMH
  3. 使用 JMH 测试 replace
  4. JMH 基本概念
  5. 源码 & 课后题

String.replace vs StringUtils.replace

字符串的 replace 是我们平时最常用的操作了,那么你用对了吗?我们下面就快速的比较一下 String.replaceStringUtils.replace 的性能,你就会发现平时用的对不对了。

Benchmark                 Mode  Cnt         Score          Error  UnitslongString1Match         thrpt   21   1065385.376 ±   163542.395  ops/slongString1MatchUtils    thrpt   21   5796658.817 ±   402075.454  ops/slongStringNMatch         thrpt   21    836951.534 ±   184212.932  ops/slongStringNMatchUtils    thrpt   21   2604916.198 ±  1151573.761  ops/slongStringNoMatch        thrpt   21   2664613.208 ±   812092.909  ops/slongStringNoMatchUtils   thrpt   21  13064807.201 ±  5216438.357  ops/sshortString1Match        thrpt   21   1666278.062 ±   847582.653  ops/sshortString1MatchUtils   thrpt   21   8487720.328 ±  4039195.570  ops/sshortStringNMatch        thrpt   21   2160392.894 ±   326624.549  ops/sshortStringNMatchUtils   thrpt   21   7579329.122 ±  1555644.322  ops/sshortStringNoMatch       thrpt   21   4644501.698 ±  1052814.151  ops/sshortStringNoMatchUtils  thrpt   21  92366435.842 ± 31333158.039  ops/s

上面的内容是通过 JMH 输出的基准测试比较,我们可以清楚的看到 String.replaceStringUtils.replace 的差距是非常大,尤其是没有匹配项的时候,我们可以简单通过源码查看其原因,StringUtils 在替换的时候会优先判断是否有匹配然后再循环,这样减少了很多无用的操作,当然 JDK9 以后也做了这个功能的优化,所以如果你使用的是 9+ 可以忽略这个性能问题。

int end = searchText.indexOf(searchString, start);if (end == INDEX_NOT_FOUND) {    return text;}

其次呢,JDK 的 replace 里面使用的正则也是一个非常耗时测操作。

return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(this).replaceAll(Matcher.quoteReplacement(replacement.toString()));

好了说到这里基本知道平时替换字符串怎么做了吧,这个可不是大题小做,正所谓不积跬步无以至千里,那么接下来就来说说我们上文中的测试是怎么实现的。

什么是 JMH

Java Microbenchmark Harness, 由JIT开发人员开发的,于2013年发布的一款基于Java的微基准测试工具,现已归于JDK。官网:http://openjdk.java.net/projects/code-tools/jmh/
JMH主要用于量化Java代码的性能,主要应用场景如下:

  • 对于已知的函数进行进一步优化
  • 确认函数执行时间以及于参数的关系
  • 比较相同功能的函数的性能

所以我们上面就是它的第三使用场景

使用 JMH 测试 replace

在 JDK9 以前需要手工引入如下两个包来引入相关的能力,目前最新的版本是 1.23

<dependency>    <groupId>org.openjdk.jmhgroupId>    <artifactId>jmh-coreartifactId>    <version>1.23version>dependency><dependency>    <groupId>org.openjdk.jmhgroupId>    <artifactId>jmh-generator-annprocessartifactId>    <version>1.23version>dependency>

然后编写测试类 StringReplaceBenchmark.java,主要包括 2 * 3 个测试用例,分别是长字符串、短字符串配合有配字符、无匹配字符和多个匹配字符的测试。编写完成以后直接运行 main 方法即可,运行过程中会打印很多中间内容如下,为了让测试更准确,所以有一个预热环节,因为 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译成为机器码从而提高执行速度。为了让 benchmark 的结果更加接近真实情况就需要进行预热。同时增加了 7 次迭代取平均值,这样测试才更有说服力。

# Warmup Iteration   1: 300005.882 ops/s# Warmup Iteration   2: 542183.082 ops/s# Warmup Iteration   3: 944105.027 ops/s# Warmup Iteration   4: 791354.184 ops/s# Warmup Iteration   5: 692452.146 ops/sIteration   1: 872745.904 ops/sIteration   2: 1195916.316 ops/sIteration   3: 896864.162 ops/sIteration   4: 1009119.818 ops/sIteration   5: 1156199.641 ops/sIteration   6: 1150938.109 ops/sIteration   7: 952645.233 ops/s

下面就是全部的源码

@Fork(value = 3, jvmArgsAppend = "-Djmh.stack.lines=3")@Warmup(iterations = 5)@Measurement(iterations = 7)public class StringReplaceBenchmark {

    private static final String SHORT_STRING_NO_MATCH = "abc";    private static final String SHORT_STRING_ONE_MATCH = "a'bc";    private static final String SHORT_STRING_SEVERAL_MATCHES = "'a'b'c'";    private static final String LONG_STRING_NO_MATCH =            "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc";    private static final String LONG_STRING_ONE_MATCH =            "abcabcabcabcabcabcabcabcabcabcabca'bcabcabcabcabcabcabcabcabcabcabcabcabc";    private static final String LONG_STRING_SEVERAL_MATCHES =            "abcabca'bcabcabcabcabcabc'abcabcabca'bcabcabcabcabcabca'bcabcabcabcabcabcabc";

    @Benchmark    public void shortStringNoMatch(Blackhole blackhole) {        blackhole.consume(SHORT_STRING_NO_MATCH.replace("'", "''"));    }

    @Benchmark    public void shortStringNoMatchUtils(Blackhole blackhole) {        blackhole.consume(StringUtils.replace(SHORT_STRING_NO_MATCH, "'", "''"));    }

    @Benchmark    public void longStringNoMatch(Blackhole blackhole) {        blackhole.consume(LONG_STRING_NO_MATCH.replace("'", "''"));    }

    @Benchmark    public void longStringNoMatchUtils(Blackhole blackhole) {        blackhole.consume(StringUtils.replace(LONG_STRING_NO_MATCH, "'", "''"));    }

    @Benchmark    public void shortString1Match(Blackhole blackhole) {        blackhole.consume(SHORT_STRING_ONE_MATCH.replace("'", "''"));    }

    @Benchmark    public void shortString1MatchUtils(Blackhole blackhole) {        blackhole.consume(StringUtils.replace(SHORT_STRING_ONE_MATCH, "'", "''"));    }

    @Benchmark    public void longString1Match(Blackhole blackhole) {        blackhole.consume(LONG_STRING_ONE_MATCH.replace("'", "''"));    }

    @Benchmark    public void longString1MatchUtils(Blackhole blackhole) {        blackhole.consume(StringUtils.replace(LONG_STRING_ONE_MATCH, "'", "''"));    }

    @Benchmark    public void shortStringNMatch(Blackhole blackhole) {        blackhole.consume(SHORT_STRING_SEVERAL_MATCHES.replace("'", "''"));    }

    @Benchmark    public void shortStringNMatchUtils(Blackhole blackhole) {        blackhole.consume(StringUtils.replace(SHORT_STRING_SEVERAL_MATCHES, "'", "''"));    }

    @Benchmark    public void longStringNMatch(Blackhole blackhole) {        blackhole.consume(LONG_STRING_SEVERAL_MATCHES.replace("'", "''"));    }

    @Benchmark    public void longStringNMatchUtils(Blackhole blackhole) {        blackhole.consume(StringUtils.replace(LONG_STRING_SEVERAL_MATCHES, "'", "''"));    }

    public static void main(String[] args) throws RunnerException {        Options options = new OptionsBuilder().include(StringReplaceBenchmark.class.getSimpleName()).build();        new Runner(options).run();    }}

说了这么多源码,里面用来很多新的技术点,下面我们就简单解释下,这样后面你就可以自己做测试了。

JMH 基本概念

@BenchmarkMode

基准测试类型:

  • Throughput: 整体吞吐量,例如“1秒内可以执行多少次调用”。
  • AverageTime: 调用的平均时间,例如“每次调用平均耗时xxx毫秒”。
  • SampleTime: 随机取样,最后输出取样结果的分布,例如“99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内”
  • SingleShotTime: 以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为0,用于测试冷启动时的性能。
  • All(“all”, “All benchmark modes”);

@Warmup

一般我们前几次进行程序测试的时候都会比较慢, 所以要让程序进行几轮预热,保证测试的准确性。

  • iterations就是预热轮数。
  • time则是每次预热时间。
  • batchSize:批处理大小,每次操作调用几次方法。

@Measurement

度量,其实就是一些基本的测试参数。

  • iterations 进行测试的轮次
  • time 每轮进行的时长
  • timeUnit 时长单位

@Threads

每个进程中的测试线程,可用于类或者方法上。一般选择为cpu乘以2。如果配置了 Threads.MAX ,代表使用 Runtime.getRuntime().availableProcessors() 个线程。

@Fork

可用于类或者方法上。如果 fork 数是2的话,则 JMH 会 fork 出两个进程来进行测试。

@OutputTimeUnit

这个比较简单了,基准测试结果的时间类型。一般选择秒、毫秒、微秒。

@Benchmark

方法级注解,表示该方法是需要进行 benchmark 的对象,用法和 JUnit 的 @Test 类似,就是我们上面的示例中的 6 个方法。

@Param

@Param 可以用来指定某项参数的多种情况,直接把它注解到变量上,就可以做循环测试了。

@Setup

方法级注解,这个注解的作用就是我们需要在测试之前进行一些准备工作,可以类比为 @Before

@TearDown

方法级注解,这个注解的作用就是我们需要在测试之后进行一些结束工作,比如关闭线程池,数据库连接,流等。

Runner

全部准备好了以后需要运行,所以用下面的 Runner 进行运行,同时传递 Options

Options options =   new OptionsBuilder()  .include(StringReplaceBenchmark.class.getSimpleName())  .build();  new Runner(options).run();

最常用的几个方法

  • include 正则包含的要测试类名,当前例子已经确定,所以就是类的 simpleName
  • output 如果传入会把结果输出到文件,比如 ~/benchmark.log
  • forks 重新配置 fork 的数量,会覆盖之前的配置
  • warmupIterations 预热次数,会覆盖之前的配置
  • measurementIterations 迭代次数,会覆盖之前的配置
  • threads 线程数量,会覆盖之前的配置

源码&课后题

上文中的所有代码都存放在了下面的仓库https://github.com/juice-resume/java-programming
看了这篇文章是不是除了知道以后怎么使用 repalce ,那么是不是想动动小手试试平时用的 BeanUtils、HashMap、ArrayList 性能都是怎么样的呢?不过要记住 JMH 只适合细粒度的方法测试,并不适合复杂的系统测试。

▼往期精彩回顾▼让人又爱又恨的 Lombok,到底该不该用Delombok 是个啥?居然可破 Lombok?跳槽的必要条件是有一份好的简历时候为自己的后半生考虑了——致奔三的互联网人

点个赞呗

string取某个符号后面的的_String.replace 用的不对性能可能差 10 倍,你用对了吗?...相关推荐

  1. chatgpt赋能python:**Python取余符号:了解%运算符的作用和用法**

    Python取余符号:了解%运算符的作用和用法 作为一名有着10年编程经验的工程师,我对Python这门编程语言深有了解.在本文中,我将详细介绍Python的取余符号,即%运算符,其作用和用法. 什么 ...

  2. 05.MyBtais两种取值符号以及输入参数和输出参数

    输入参数:parameterType 两种取值符号的异同 1.类型为简单类型(8个基本类型+string) 不同点: a.#{任意值},${value} 其中的标识符只能是value b. #{}自动 ...

  3. Mybatis,#{}和${}取值符号

    如图,两个方法的参数类型为简单类型,简单类型包括8大基本类型和String 1. #{}取值符号会自动为String类型的参数加上''单引号 2. ${}取值符号不会自动为String加上''单引号 ...

  4. seg是伪操作符,用来取后面符号的段地址

    mov ax,seg area中的seg是什么意思? 可以写作mov ax,area吗? 不可以,seg是伪操作符,用来取后面符号的段地址,如果不加seg则取其内容. mov dx,offset ar ...

  5. C++中如何区分引用和取地址符号?

    引用是在写函数定义的时候用,调用的时候直接写,没有取地址符&, 指针是在函数定义的时候用*,调用的时候用取地址符号&,例如: void funcA(int& a,int& ...

  6. 关于取反符号的相关问题

    取反符号(~) 作用:将数字转换成二进制数,然后按位取反. 如: 十进制数 5 转换成二进制是0101(此时显示出来的是原码) 取反结果是 1010(此时显示出来的是补码) 将补码转换成原码(先减1, ...

  7. python取整符号_python 取整

    广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! (1)向下取整向下取整很简单,直接使用int()函数即可,如下代码(python ...

  8. 关于取整符号(二叉树具有n个结点的m次树的最小高度⌈logₘ(n(m-1)+1)⌉)

    性质4 具有n个结点的m次树的最小高度为 关于取整符号:向上取整⌈⌉和向下取整⌊⌋符号 向下取整的运算称为Floor,用数学符号 ⌊ ⌋ 表示:向上取整的运算称为Ceiling,用数学符号 ⌈ ⌉ 表 ...

  9. word插入向上向下取整符号

    Word->插入->符号,在字体中选择Lucida Sans Unicode 子集选择数学运算符 就能找到向上 .向下取整符号

  10. word里输入向上或向下取整符号

    Word->插入->符号,在字体中选择Lucida Sans Unicode 子集选择数学运算符 找到向上.向下取整符号

最新文章

  1. CSS position财产
  2. POJ 1840 Eqs 解方程式, 水题 难度:0
  3. 组合键 发送指定信号_Django signal 信号机制的使用
  4. tableau实战系列(十八)-通过可视化实现购物篮关联分析( Market Basket Analysis),关联物品之间的关联关系
  5. [MATLAB粒子模拟笔记]初始化半个时间步的位置
  6. caffe 测试时间报错 Aborted at unix time
  7. Java常用API(六)Date 日期类介绍及使用
  8. java多线程同时运行_Java实现的两个线程同时运行案例
  9. s7-1200跟mysql_让西门子S7-1200直接连接MySQL数据库!!!
  10. 使用bootstrap的相关配置
  11. open-falcon之query
  12. python requests text content_python 3 关于requests库的 text / content /json
  13. 【dfs+简单贪心】Leaf Sets【Codeforces Round #510 (Div. 2)】
  14. 戴尔r510服务器修复,DELL R510服务器宕机案例(1)
  15. android apk加密技术,android apk 自我保护技术-加密apk
  16. 如何设计更好的脉搏血氧仪:实施
  17. tp1900芯片对比7621a_TP无线路由器WDR7660千兆版,厉害了单芯片TP1900
  18. 保姆级上云教程:购买百度云BCC服务器以及之后的一些注意事项
  19. 他是阿里顶尖科学家,扛起国产分布式数据库大旗,性能超Oralce 20倍!
  20. 阿里云服务器上搭建宝塔

热门文章

  1. python获取当前路径的方法
  2. 14-08-08 考核试题总结~
  3. Linux下的shell编程(二)BY 四喜三顺
  4. 逻辑卷、物理卷、卷组
  5. UNITY_DOTWEEN_PATH路径动画的使用
  6. python函数调用时所提供的参数可以是常量_如何使用mock作为函数参数在Python中修补常量...
  7. WPF学习之数据绑定
  8. js中去除字符串中所有的html标签
  9. 修改mysql数据存放路径
  10. FlexDisPlayRoom正在发货「可在线玩弄」