Łączenie Stringów, a wydajność – dlaczego używać StringBuilder’a
Ostatnio wpadł mi w ręce kod, który robił jedną rzecz: łączył Stringi, w wyniku czego powstawał duży String. Można i tak.
Robione to było za pomocą StringBuilder’a, a więc dobrze.
Powszechną opinią jest, że trzeba używać StringBuilder’a, bo jest szybszy. O ile szybszy ? Czy zawsze? Sprawdźmy 🙂
Weźmy najpierw na warsztat zwykłe łączenie Stringów za pomocą operatora + :
1 2 3 4 5 6 7 8 9 |
final String message = "I will grow to be BIG!!!"; String iWillGrowToBeBig = message; Date startTime = new Date(); for (int a=0;a<20000;a++) { iWillGrowToBeBig = iWillGrowToBeBig + " "+ message; } System.out.println(iWillGrowToBeBig.length()); Date endTime = new Date(); System.out.println("operation with simple string concat took "+(endTime.getTime() - startTime.getTime())+ "ms"); |
Co to to robi? Dodaje do siebie w pętli 20 tysięcy razy jakiś tekst. Ile to zajmuje czasu? U mnie w okolicach 4 sekund. Szybko?
A teraz weźmy StringBuildera:
1 2 3 4 5 6 7 8 9 10 |
final String stringBuilderMessage = "I will grow to be BEST!!"; StringBuilder iWillGrowToBeBigWithStringBuilder = new StringBuilder(stringBuilderMessage); Date startTimeWithStringBuilder = new Date(); for (int a=0;a<20000;a++) { iWillGrowToBeBigWithStringBuilder.append(" " + stringBuilderMessage); } final String string = iWillGrowToBeBigWithStringBuilder.toString(); System.out.println(string.length()); Date endTimeWithStringBuilder = new Date(); System.out.println("operation with stringbuilder took "+(endTimeWithStringBuilder.getTime() - startTimeWithStringBuilder.getTime())+ "ms"); |
Czas wykonania:
1-10ms!
To nie jest szybciej. To jest cholernie szybciej! 😉
Ale czy taka różnica będzie zawsze?
Popatrzmy na dokumentację: http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.18.1
Zwłaszcza na tekst pisany małym drukiem (okazuje się, że nawet czytając dokumentację, trzeba uważać na to, co jest pisane małymi literkami 🙂 ):
An implementation may choose to perform conversion and concatenation in one step to avoid creating and then discarding an intermediate String
object. To increase the performance of repeated string concatenation, a Java compiler may use the StringBuffer
class or a similar technique to reduce the number of intermediate String
objects that are created by evaluation of an expression.
Wynika z tego, że na etapie kompilacji kompilator może zoptymalizować kod i zamienić + na coś bardziej wydajnego. Może, ale nie musi.
W przypadku dodawania Stringów w pętli, kompilator nie potrafi dokonać optymalizacji. Stąd potrzeba użycia StringBuilder’a. Prawdopodobnie Java 9 coś w tej kwestii zmieni: http://openjdk.java.net/jeps/280