Das Thema Quellcode Optimierung geht “Hand in Hand” mit dem Thema Clean Code Development. Denn wenn ich sauberen Code schreibe, dann ist die Chance groß das dieser auch performant ist, da ich in der Regel mich von Ballast getrennt habe.
- java.lang.String
- java.util.Date
- java.util.Calendar
- java.text.SimpleDateFormat
- Boxing und Unboxing
- Map
java.lang.String
Verwendet man Strings so werden diese in den sogenannten StringPool abgelegt, dieser StringPool ist eine Map<string,string> welche zur gesammten laufzeit der Anwendung existiert und für jeden Thread gleichbleibend ist.
Nehmen wir folgendes kleines Beispiel:
public class Test { private static final String HALLO = "Hallo Welt!"; private void test() { String test = "Hallo Welt!"; System.out.println("test() ->" + (HALLO == test)); } private void testThread() { new Thread(new Runnable() { @Override public void run() { String test = "Hallo Welt!"; System.out.println("testThread() ->" + (HALLO == test)); } }).start(); } public static void main(String... args){ new Test().test(); new Test().testThread(); } }
wie wird das Ergebnis sein ?
test() ->true testThread() ->true
Was uns gleich zu der ersten Optimierungsmöglichkeit bringt , werden Strings häufig verwendet, so sollten diese explizit “private static final” sein.
String.split, String.matches, String.replaceAll, String.replaceFirst
Sollten Sie Strings verändern wollen und dazu Reguläreausdrücke (RegEx) verwenden wollen, so empfiehlt es sich die Klassen Matcher und Pattern dazu zu verwenden da diese sicherer im Umgang mit Regulärenausdrücken sind.
Es ist erwiesen, dass ein per Handgeschriebenes ersetzten um den Faktor 10 langsamer ist als mit den Matcher und Pattern Klassen.
java.util.Date
Die Klasse java.util.Date sollte nur in ausnahmefällen verwendet werden da diese nur eine Wrapperklasse für den Timerstamp “long” darstellt.
java.util.Calendar
Für die Berechnung und Verarbeitung von Datumsangaben sowie die Internationalisierung von denen ist die Klasse java.util.Calender bestens geeignet jedoch erzeugt die schon bei der Erstellung einen riesen Overhead an Speicherbedarf ohne das ein Zugriff auf eine Funktion erfolgt ist.
java.text.SimpleDateFormat
Das Formatieren und Parsen von Datums werten übernimmt die Klasse java.text.SimpleDateFormat.
Für die Verwendung sollte man beachten das, wenn diese Klasse mehrmals verwendet wird ein Aufruf erfolgen sollte, denn das Erstellen dieser Klasse benötigt schon eine Menge Zeit und Ressourcen.
Hierzu auch ein kleines Beispiel:
public class SimpleDateFormatBeispiel { private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy"); private void test() { long before = System.currentTimeMillis(); for (long i = 0L; i < 10000; i++) { DATE_FORMAT.format(new Date(i)); } long after = System.currentTimeMillis(); System.out.println("test() -> Zeit:" + (after - before) + " ms."); } private void testNewInstance() { long before = System.currentTimeMillis(); for (long i = 0L; i < 10000; i++) { SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy"); dateFormat.format(new Date(i)); } long after = System.currentTimeMillis(); System.out.println("testNewInstance() -> Zeit:" + (after - before) + " ms."); } public static void main(String... args) { new SimpleDateFormatBeispiel().test(); new SimpleDateFormatBeispiel().testNewInstance(); } }
Sicherlich ist das Beispiel etwas überzogen, da, jeder vernünftige Programmierer von selbst darauf kommen würde, jedoch kommt es in einer Batchverarbeitung schon einmal ungewollt zu so einem Ergebnis.
Hier noch zur Vollständigkeit die Ausgabe welche auch keine Überraschung darstellt:
test() -> Zeit:176 ms. testNewInstance() -> Zeit:332 ms.
Boxing und Unboxing
Wenn man von einer Wrapperklasse zbsp. Integer den primitiven Datentyp int haben möchte oder umgekehrt so ist das in Java relativ einfach da dieses Boxing bzw. unboxing übernommen wird wir brauchen nur:
int a = new Integer(12);
schreiben.
Hier nun ein Beispiel :
public class BoxingUnboxing { private void boxing(){ Integer a = 12; } private void unBoxing(){ int a = new Integer(12); } private void strToIntSlow(){ long before = System.nanoTime(); int a = Integer.valueOf("12"); long after = System.nanoTime(); System.out.println("strToIntSlow() -> Zeit:" + (after - before) + " ns."); } private void strToIntFast(){ long before = System.nanoTime(); int a = Integer.parseInt("12"); long after = System.nanoTime(); System.out.println("strToIntFast() -> Zeit:" + (after - before) + " ns."); } public static void main(String... args){ new BoxingUnboxing().boxing(); new BoxingUnboxing().unBoxing(); new BoxingUnboxing().strToIntSlow(); new BoxingUnboxing().strToIntFast(); } }
Wie zuerkennen ist muss hier mit “System.nanoTime();” gearbeitet werden da dieser Vorgang schon relativ schnell ist. Jedoch die Summe aus dem ganzen ist doch schon erschreckend :
strToIntSlow() -> Zeit:15971 ns. strToIntFast() -> Zeit:5436 ns.
An diesem Beispiel kann man gut erkennen, was das “einfache” verwenden einer anderen Methode schon bewirken kann.
java.util.Map
Contains vrs. get & null Check
Hat man eine Map mit vielen Einträgen, so kann man, wenn man einen Eintrag sucht die Methode “contains(Object key)” verwenden, jedoch ist es effizienter einen wahrscheinlich vorhandenen Wert zu holen und diesen auf NULL zu prüfen.
Hierzu ein “kleines” Beispiel:
public class MapContainsBeispiel { private static final Map<integer, string=""> ORTE = new HashMap<integer, string="">(); static { ORTE.put(38364, "Schöningen"); ORTE.put(39576, "Stendal"); ORTE.put(38152, "Nienburg/Weser"); ORTE.put(38144, "Braunschweig"); ORTE.put(38446, "Wolfsburg"); ORTE.put(16259, "Altreetz"); ORTE.put(76707, "Hambrücken"); ORTE.put(19273, "Amt Neuhaus"); ORTE.put(49451, "Holdorf"); ORTE.put(26180, "Rastede"); ORTE.put(26215, "Wiefelstede"); ORTE.put(48527, "Nordhorn"); } private void testContains() { long before = System.nanoTime(); boolean result = ORTE.containsKey(39576); long after = System.nanoTime(); System.out.println("testContains() -> Zeit:" + (after - before) + " ns."); } private void testGetNullCheck() { long before = System.nanoTime(); String result = ORTE.get(39576); if (result != null){ //tue etwas } long after = System.nanoTime(); System.out.println("testGetNullCheck() -> Zeit:" + (after - before) + " ns."); } public static void main(String... args) { new MapContainsBeispiel().testContains(); new MapContainsBeispiel().testGetNullCheck(); } }
Möchte man nun nach einem ORT anhand der Postleitzahl suchen, so ergibt sich folgendes Ergebnis :
testContains() -> Zeit:8155 ns. testGetNullCheck() -> Zeit:6457 ns.
Auch hier spricht das Ergebnis für sich selbst, auch wenn es nur in Nanosekunden Bereich sind so ist doch die Summe des ganzen zu betrachten.
TMap
Wenn man ein Map instaziiert so belegt diese schon einige KiloByte an Speicherplatz obwohl noch keine Werte vorhanden sind, möchte man dieses “Problem” lösen so kann man auf das externe Paket Trove4J zurückgreifen.
Dieses Paket hat so ziemlich alle Collections, Sets und Maps implementiert und das ganze sehr Speicherfreundlich umgesetzt.