Premature optimization is the root of all evil (Donald Knuth).
And it kills simplicity.
How to correctly implement Java string concatenations was a long discussed developer topic.
What do you think is faster, this StringBuilder
technique ...
return new StringBuilder() .append("Hello World, current date and time is ") .append(new Date()) .append(" and we are at time zone ") .append(TimeZone.getDefault()) .toString();
... or this plus-operator technique:
return "Hello World, current date and time is "+ new Date()+ " and we are at time zone "+ TimeZone.getDefault();
?
Most people would say, the plus-operator was discouraged in the Java beginning years, thus the first snippet is faster. Wrong, they are equally fast. Here are some measurements over exactly these snippets, with warm-up and 1000000 repeats, results are in milliseconds:
Technique | Test 1 | Test 2 | Test 3 | Test 4 |
---|---|---|---|---|
StringBuilder | 917 | 939 | 1025 | 992 |
Plus-Operator | 863 | 903 | 1045 | 996 |
But what about StringBuffer
, is this faster?
No, it is not, it is a little slower than StringBuilder
, but that depends.
Surprisingly in some cases the plus-operator is much faster
than both StringBuilder
and StringBuffer
,
but sometimes it is also ways slower.
It depends on how the concatenation is done,
in a loop, or in a programmed sequence.
Loop concatenation (STRING_APPEND_COUNT = 4
):
StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < STRING_APPEND_COUNT; i++) stringBuilder.append(string); return stringBuilder.toString();
String s = ""; for (int i = 0; i < STRING_APPEND_COUNT; i++) s += string; return s;
Sequence concatenation:
StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(string); stringBuilder.append(string); stringBuilder.append(string); stringBuilder.append(string); return stringBuilder.toString();
String s = ""+ string+ string+ string+ string; return s;
The used string
in these examples is not final.
Here are results:
Technique | Test 1 | Test 2 | Test 3 | Test 4 |
---|---|---|---|---|
StringBuilder Loop | 490 | 379 | 449 | 432 |
Plus-Operator Loop | 776 | 623 | 828 | 802 |
StringBuilder Sequence | 319 | 337 | 420 | 348 |
Plus-Operator Sequence | 228 | 260 | 265 | 262 |
StringBuilder
is much faster in loops,
but in sequences the plus-operator wins.
When I make string
final, following can be seen:
Technique | Test 1 |
---|---|
StringBuilder Loop | 321 |
Plus-Operator Loop | 689 |
StringBuilder Sequence | 210 |
Plus-Operator Sequence | 90 |
A dramatic win of plus-operator in sequences.
This is because the compiler optimizes such code chunks.
Everything else must be done at runtime, where the StringBuilder
wins.
If you want to hack more, here is the complete source
of my experiment, it also includes StringBuffer
(click to expand):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | public class StringConcatPerformanceTest { public static void main(String[] args) { StringConcatPerformanceTest test = new StringConcatPerformanceTest(); test.start(Usage.LOOP); test.start(Usage.SEQUENCE); } private enum Technique // the string-concat technique to test { STRING_BUFFER, STRING_BUILDER, PLUS_OPERATOR, } private enum Usage // the way how to use the class to test { SEQUENCE, LOOP, } private /*final*/ String string = "Hello"; private long time1; private long time2; private long time3; public StringConcatPerformanceTest() { for (Technique technique : Technique.values()) // warm up for (Usage usage : Usage.values()) run(technique, usage); } public void start(Usage usage) { time1 = time2 = time3 = 0L; for (int i = 0; i < 1000000; i++) run(usage); System.out.println("StringBuffer "+usage+": "+duration(time1)); System.out.println("StringBuilder "+usage+": "+duration(time2)); System.out.println("Plus Operator "+usage+": "+duration(time3)); System.out.println("===================="); } private String duration(long nanos) { return ""+Math.round((double)nanos / 1000000)+" millis"; } private void run(Usage usage) { time1 += run(Technique.STRING_BUFFER, usage); time2 += run(Technique.STRING_BUILDER, usage); time3 += run(Technique.PLUS_OPERATOR, usage); time2 += run(Technique.STRING_BUILDER, usage); time3 += run(Technique.PLUS_OPERATOR, usage); time1 += run(Technique.STRING_BUFFER, usage); time3 += run(Technique.PLUS_OPERATOR, usage); time1 += run(Technique.STRING_BUFFER, usage); time2 += run(Technique.STRING_BUILDER, usage); } private long run(Technique technique, Usage usage) { final long time = System.nanoTime(); if (usage == Usage.SEQUENCE) { switch (technique) { case STRING_BUFFER: stringBufferSequence(); break; case STRING_BUILDER: stringBuilderSequence(); break; case PLUS_OPERATOR: plusOperatorSequence(); break; default: throw new IllegalArgumentException(); } } else { // Usage.LOOP switch (technique) { case STRING_BUFFER: stringBufferLoop(); break; case STRING_BUILDER: stringBuilderLoop(); break; case PLUS_OPERATOR: plusOperatorLoop(); break; default: throw new IllegalArgumentException(); } } return System.nanoTime() - time; } private static final int STRING_APPEND_COUNT = 4; private String stringBufferLoop() { StringBuffer stringBuffer = new StringBuffer(); for (int i = 0; i < STRING_APPEND_COUNT; i++) stringBuffer.append(string); return stringBuffer.toString(); } private String stringBuilderLoop() { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < STRING_APPEND_COUNT; i++) stringBuilder.append(string); return stringBuilder.toString(); } private String plusOperatorLoop() { String s = ""; for (int i = 0; i < STRING_APPEND_COUNT; i++) s += string; return s; } private String stringBufferSequence() { StringBuffer stringBuffer = new StringBuffer(""); stringBuffer.append(string); // 1 stringBuffer.append(string); stringBuffer.append(string); stringBuffer.append(string); // 4 == STRING_APPEND_COUNT return stringBuffer.toString(); } private String stringBuilderSequence() { StringBuilder stringBuilder = new StringBuilder(""); stringBuilder.append(string); // 1 stringBuilder.append(string); stringBuilder.append(string); stringBuilder.append(string); // 4 == STRING_APPEND_COUNT return stringBuilder.toString(); } private String plusOperatorSequence() { String s = "" + string + // 1 string + string + string ; // 4 == STRING_APPEND_COUNT return s; } } |
I would always use the plus-operator by default.
It is the much more readable and simpler variant.
Just in cases where you really build big texts,
and non-final fields or method calls are involved,
you should use StringBuilder
.
Keine Kommentare:
Kommentar veröffentlichen