收缩字符串时的程序效率与使用/编辑原始字符串制作新字符串时的程序效率

Program efficency when contracting Strings vs making a new String w/ edits to the original one

我只是想知道以下两个代码的效率:

public void appendInput(String s, boolean isCommand) {

    s = "\n" + (isCommand == true ? ">" : "") + s + "\n";
    getConsoleInput().append(s);

    //vs

    getConsoleInput().append("\n" + (isCommand == true ? ">" : "") + s + "\n");

}

根据我对这门语言的基本掌握,第二种变体是否会比第二种变体更有效,因为它不创建新对象 (String)?或者当你收缩两个 String 时它会创建一个新的 String 吗?我认同。我只需要比我更了解 Java 的人。

所以我决定 运行 一些测试。这是我想出的:

public class Main {

public static void main(String[] args) {
    String s;
    Boolean b;
    long startTime;
    long endTime;
    long[] results = new long[4];

    System.out.println("*ALL TESTS WILL BE LOOPED *1000");

    sep();

    System.out.println("METHOD 1;-- TEST 1 - STRING = \"TESTING \" && BOOLEAN = FALSE:");

    s = "TESTING";
    b = false;  // change variables

    startTime = System.currentTimeMillis(); //commence testing from here

    for(int i = 0; i < 1000; i ++) {
        s = "" + (b ? ">" : "") + s + "";
        System.out.print(s);
    }

    endTime = System.currentTimeMillis();
    results[0] = endTime - startTime;
    System.out.println("Total execution time: " + results[0] + "ms");

    sep();

    //Next test

    System.out.println("METHOD 1;-- TEST 2 - STRING = \"TESTING \" && BOOLEAN = TRUE:");

    s = "TESTING";
    b = false;

    startTime = System.currentTimeMillis();

    for(int i = 0; i < 1000; i ++) {
        s = "" + (b ? ">" : "") + s + "";
        System.out.print(s);
    }

    endTime = System.currentTimeMillis();
    results[1] = endTime - startTime;
    System.out.println("Total execution time: " + results[1] + "ms");
    sep();

    //Next test

    System.out.println("METHOD 2;-- TEST 1 - STRING = \"TESTING \" && BOOLEAN = FALSE:");

    s = "TESTING";
    b = false;  // change variables

    startTime = System.currentTimeMillis(); //commence testing from here

    for(int i = 0; i < 1000; i ++) {
        System.out.print("" + (b == true ? ">" : "") + s + "");
    }

    endTime = System.currentTimeMillis();
    results[2] = endTime - startTime;
    System.out.println("Total execution time: " + results[2] + "ms");
    sep();

    //Next test

    System.out.println("METHOD 2;-- TEST 2 - STRING = \"TESTING \" && BOOLEAN = TRUE:");

    s = "TESTING";
    b = false;

    startTime = System.currentTimeMillis();

    for(int i = 0; i < 1000; i ++) {
        System.out.print("" + (b == true ? ">" : "") + s + "");
    }

    endTime = System.currentTimeMillis();
    results[3] = endTime - startTime;
    System.out.println("Total execution time: " + results[3] + "ms");
    sep();

    System.out.println("RESULTS:");
    System.out.println("-----------------------");

    String[] typesOfTests = {"METHOD 1 BOOLEAN = FALSE", "METHOD 1 BOOLEAN = TRUE  ",
            "METHOD 2 BOOLEAN = FALSE", "METHOD 2 BOOLEAN = TRUE  "};

    for(int i = 0; i < typesOfTests.length && i < results.length; i++) {
        System.out.print(typesOfTests[i]);
        System.out.print("\t");
        System.out.println(results[i]);
    }
}

private static void sep() {
    System.out.println("====================================");
}
private void optOne(String s, boolean b) {
    s = "\n" + (b == true ? ">" : "") + s + "\n";
    System.out.println(s);
}

private void optTwo(String s, boolean b) {
    System.out.println("\n" + (b == true ? ">" : "") + s + "\n");

}

}

这是一些结果...

ALL TESTS WILL BE LOOPED *1000
====================================
METHOD 1;-- TEST 1 - STRING = "TESTING " && BOOLEAN = FALSE:
TESTING *1000 ...
Total execution time: 25ms
====================================
METHOD 1;-- TEST 2 - STRING = "TESTING " && BOOLEAN = TRUE:
TESTING *1000 ...
Total execution time: 17ms
====================================
METHOD 2;-- TEST 1 - STRING = "TESTING " && BOOLEAN = FALSE:
TESTING *1000 ...
Total execution time: 4ms
====================================
METHOD 2;-- TEST 2 - STRING = "TESTING " && BOOLEAN = TRUE:
TESTING *1000 ...
Total execution time: 8ms
====================================
RESULTS:
-----------------------
METHOD 1 | BOOLEAN = FALSE  | 25
METHOD 1 | BOOLEAN = TRUE   | 17
METHOD 2 | BOOLEAN = FALSE  | 4
METHOD 2 | BOOLEAN = TRUE   | 8

和其他一些(好的,好的,仅 table!)

METHOD 1 | BOOLEAN = FALSE  | 19
METHOD 1 | BOOLEAN = TRUE   | 10
METHOD 2 | BOOLEAN = FALSE  | 5
METHOD 2 | BOOLEAN = TRUE   | 5

METHOD 1 | BOOLEAN = FALSE  | 18
METHOD 1 | BOOLEAN = TRUE   | 11
METHOD 2 | BOOLEAN = FALSE  | 5
METHOD 2 | BOOLEAN = TRUE   | 4

METHOD 1 | BOOLEAN = FALSE  | 20
METHOD 1 | BOOLEAN = TRUE   | 16
METHOD 2 | BOOLEAN = FALSE  | 6
METHOD 2 | BOOLEAN = TRUE   | 4

结论:

方法 1 (s = "" + (b ? ">" : "") + s + ""; System.out.print(s);) 比方法 2 (System.out.print("" + (b == true ? ">" : "") + s + "");) 快得多,因为不需要创建新的 String 对象。

我注意到的另一个趋势是,当 booleanfalse 时,两种方法中的时间增加了三分之一以上,即使在方法 1 中比在方法 1 中更明显方法 2. 我不知道为什么会这样... 谁能给我解释一下?


TL;DR 方法 1 较慢,因为需要创建新的 String 对象。此外,当 boolean 等于 false 时,两种情况下执行方法所花费的时间都会增加。你能给我解释一下吗?

编写基准测试 "with your bare hands" 由于 JVM 的工作方式,大多数情况下是无用的。您可以获得截然不同的结果。要走的路是 运行 你的代码 很多次 当你的字节码被 JVM 正确优化时,这发生在 运行 代码一段时间后。这就是 JMH framework 的意思。 如果您需要正确地对代码进行基准测试,请始终使用它,尤其是对于像您这样的微基准测试。

所以我自己进行了一些测试(看看 JMH 代码变得多么简单):

package org.sample;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.infra.Blackhole;

public class StringsAppendBenchmark {
    private static String TESTING = "TESTING";

    @Benchmark
    @Fork(3)
    public void withVariable(Blackhole bh) {
        String s = "\n" + ">" + TESTING + "\n";
        bh.consume(s);
    }

    @Benchmark
    @Fork(3)
    public void withoutVariable(Blackhole bh) {
        bh.consume( "\n" + ">" + TESTING + "\n" );
    }
}

现在,使用 System.out.println() 进行基准测试并不是很有用,因为最终的性能在很大程度上取决于读取应用程序 stdout 的内容。写入 /dev/null 的时间可以忽略不计,而写入 IDE 控制台可能会很快耗尽所有内存,让您等待系统换出。这就是为什么使用 Blackhole 的原因,它只接受一个参数并且不对其进行任何操作,因为该参数不会被 JVM 中的 运行time 字节码优化优化为不存在。

运行 基准输出:

Benchmark                                      Mode  Samples         Score         Error  Units
o.s.StringsAppendBenchmark.withVariable       thrpt       60  37105629.065 ± 1124455.355  ops/s
o.s.StringsAppendBenchmark.withoutVariable    thrpt       60  38059994.264 ± 1021414.039  ops/s

结果和误差范围告诉我们,是否预先将其存储在变量中并不重要。果然不出所料,说实话。