收缩字符串时的程序效率与使用/编辑原始字符串制作新字符串时的程序效率
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
对象。
我注意到的另一个趋势是,当 boolean
为 false
时,两种方法中的时间增加了三分之一以上,即使在方法 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
结果和误差范围告诉我们,是否预先将其存储在变量中并不重要。果然不出所料,说实话。
我只是想知道以下两个代码的效率:
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
对象。
我注意到的另一个趋势是,当 boolean
为 false
时,两种方法中的时间增加了三分之一以上,即使在方法 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
结果和误差范围告诉我们,是否预先将其存储在变量中并不重要。果然不出所料,说实话。