Commons Lang StringUtils.replace性能vs.String.replace

Evg*_*eev 39 java

当我比较Apache的性能StringUtils.replace()VS String.replace()我很惊讶地知道,前者是快约4倍.我使用Google的Caliper框架来衡量效果.这是我的考试

public class Performance extends SimpleBenchmark {
    String s = "111222111222";

    public int timeM1(int n) {
        int res = 0;
        for (int x = 0; x < n; x++) {
            res += s.replace("111", "333").length();
        }
        return res;
    }

    public int timeM2(int n) {
        int res = 0;
        for (int x = 0; x < n; x++) {
            res += StringUtils.replace(s, "111", "333", -1).length();
        }
        return res;
    }

    public static void main(String... args) {
        Runner.main(Performance.class, args);
    }
}
Run Code Online (Sandbox Code Playgroud)

产量

 0% Scenario{vm=java, trial=0, benchmark=M1} 9820,93 ns; ?=1053,91 ns @ 10 trials
50% Scenario{vm=java, trial=0, benchmark=M2} 2594,67 ns; ?=58,12 ns @ 10 trials

benchmark   us linear runtime
       M1 9,82 ==============================
       M2 2,59 =======
Run Code Online (Sandbox Code Playgroud)

这是为什么?这两种方法似乎都做同样的工作,StringUtils.replace()更加灵活.

nha*_*tdh 36

从源代码1:java.lang.String

public String replace(CharSequence target, CharSequence replacement) {
   return Pattern
            .compile(target.toString(), Pattern.LITERAL)
            .matcher(this )
            .replaceAll(
                    Matcher.quoteReplacement(replacement.toString()));
}
Run Code Online (Sandbox Code Playgroud)

String.replace(CharSequence target, CharSequence replacement)与实施java.util.regex.Pattern,因此,这并不奇怪,它是较慢的是2,这是与实现和.StringUtils.replace(String text, String searchString, String replacement)indexOfStringBuffer

public static String replace(String text, String searchString, String replacement) {
    return replace(text, searchString, replacement, -1);
}

public static String replace(String text, String searchString, String replacement, int max) {
    if (isEmpty(text) || isEmpty(searchString) || replacement == null || max == 0) {
        return text;
    }
    int start = 0;
    int end = text.indexOf(searchString, start);
    if (end == -1) {
        return text;
    }
    int replLength = searchString.length();
    int increase = replacement.length() - replLength;
    increase = (increase < 0 ? 0 : increase);
    increase *= (max < 0 ? 16 : (max > 64 ? 64 : max));
    StringBuffer buf = new StringBuffer(text.length() + increase);
    while (end != -1) {
        buf.append(text.substring(start, end)).append(replacement);
        start = end + replLength;
        if (--max == 0) {
            break;
        }
        end = text.indexOf(searchString, start);
    }
    buf.append(text.substring(start));
    return buf.toString();
}
Run Code Online (Sandbox Code Playgroud)

脚注

1我链接到并复制源代码的版本是JDK 7

2我链接到并复制源代码的版本是common-lang-2.5

  • 在许多情况下,偏向锁定意味着`StringBuilder`和`StringBuffer`之间的实际差异接近于零,除非你一次追加一个字符. (4认同)
  • 从Java 9开始,String.replace将比以前更快,请参阅https://bugs.openjdk.java.net/browse/JDK-8058779. (4认同)
  • 令人惊讶的是他们使用了StringBuffer。几乎可以肯定,用`StringBuilder`代替它会更快。 (2认同)

Tag*_*eev 29

在现代Java中,情况已不再如此。String.replace在得到改善的Java-9从正则表达式移动到StringBuilder的,并且甚至更改进的Java-13移动到目标的直接分配byte[]阵列预先计算其确切大小。由于使用了内部JDK功能,例如分配未初始化的数组的功能,访问String编码器的功能以及使用String避免复制的私有构造函数的功能,因此当前的实现不太可能被第三方实现所击败。

这是我使用JDK 8,JDK 9和JDK 13进行测试时的基准测试结果(caliper:0.5-rc1; commons-lang3:3.9)

Java 8(实际上慢4倍):

 0% Scenario{vm=java, trial=0, benchmark=M1} 291.42 ns; ?=6.56 ns @ 10 trials
50% Scenario{vm=java, trial=0, benchmark=M2} 70.34 ns; ?=0.15 ns @ 3 trials

benchmark    ns linear runtime
       M1 291.4 ==============================
       M2  70.3 =======
Run Code Online (Sandbox Code Playgroud)

Java 9(几乎相等的性能):

 0% Scenario{vm=java, trial=0, benchmark=M2} 99,15 ns; ?=8,34 ns @ 10 trials
50% Scenario{vm=java, trial=0, benchmark=M1} 103,43 ns; ?=9,01 ns @ 10 trials

benchmark    ns linear runtime
       M2  99,1 ============================
       M1 103,4 ==============================
Run Code Online (Sandbox Code Playgroud)

Java 13(标准方法快38%):

 0% Scenario{vm=java, trial=0, benchmark=M2} 91,64 ns; ?=5,12 ns @ 10 trials
50% Scenario{vm=java, trial=0, benchmark=M1} 57,38 ns; ?=2,51 ns @ 10 trials

benchmark   ns linear runtime
       M2 91,6 ==============================
       M1 57,4 ==================
Run Code Online (Sandbox Code Playgroud)

  • 我的第一个想法是,你会在简单的情况下获得收益,但在更复杂的情况下会损失,在这些情况下,正则表达式引擎使用 Boyer–Moore 算法会得到回报,但是后来,我注意到[你的其他答案](https://stackoverflow.com /a/33907329/2711488) 指出 `indexOf(String)` 是一个内部操作。现在,有趣的是,从“String.replace”调用是否算作动态参数的使用,或者这里是否考虑调用者的调用者…… (2认同)

小智 9

尝试这个,你会发现它比Apache的性能极其高效:

public static String replace (String source, String os, String ns) {
    if (source == null) {
        return null;
    }
    int i = 0;
    if ((i = source.indexOf(os, i)) >= 0) {
        char[] sourceArray = source.toCharArray();
        char[] nsArray = ns.toCharArray();
        int oLength = os.length();
        StringBuilder buf = new StringBuilder (sourceArray.length);
        buf.append (sourceArray, 0, i).append(nsArray);
        i += oLength;
        int j = i;
        // Replace all remaining instances of oldString with newString.
        while ((i = source.indexOf(os, i)) > 0) {
            buf.append (sourceArray, j, i - j).append(nsArray);
            i += oLength;
            j = i;
        }
        buf.append (sourceArray, j, sourceArray.length - j);
        source = buf.toString();
        buf.setLength (0);
    }
    return source;
}
Run Code Online (Sandbox Code Playgroud)

  • 它在短文本替换方面比 Apache 略快,但在长文本替换场景中不一定比其他实现快。结帐 https://github.com/greenlaw110/Benchmark4StringReplace 其中使用上面的函数完成快速实现 (2认同)

qxo*_*qxo 6

在我对JMH的测试中:https://github.com/qxo/Benchmark4StringReplace beset是loukili的方式:

java -jar target/benchmarks.jar StringReplaceBenchmark -wi 3 -i 6 -f 1 -tu ms Benchmark Mode Cnt Score Error Units StringReplaceBenchmark.test4String thrpt 6 1255.017 ± 230.012 ops/ms StringReplaceBenchmark.test4StringUtils thrpt 6 4068.229 ± 67.708 ops/ms StringReplaceBenchmark.test4fast thrpt 6 4821.035 ± 97.790 ops/ms StringReplaceBenchmark.test4lang3StringUtils thrpt 6 3186.007 ± 102.786 ops/ms

  • 好项目。我克隆了您的项目,添加了 OSGL 实现和更多测试场景。我已经向您提交了 PR (2认同)