Leistung spielt eine entscheidende Rolle für den Erfolg eines Softwareprojekts. Während der Java-Backend-Entwicklung angewendete Optimierungen sorgen für eine effiziente Nutzung der Systemressourcen und erhöhen die Skalierbarkeit Ihrer Anwendung.
In diesem Artikel werde ich einige Optimierungstechniken mit Ihnen teilen, die ich für wichtig halte, um häufige Fehler zu vermeiden.
Die Wahl einer effizienten Datenstruktur kann die Leistung Ihrer Anwendung erheblich verbessern, insbesondere beim Umgang mit großen Datenmengen oder zeitkritischen Vorgängen. Durch die Verwendung der richtigen Datenstruktur wird die Zugriffszeit minimiert, die Speichernutzung optimiert und die Verarbeitungszeit verkürzt.
Wenn Sie beispielsweise häufig in einer Liste suchen müssen, kann die Verwendung eines HashSet anstelle einer ArrayList zu schnelleren Ergebnissen führen:
// Inefficient - O(n) complexity for contains() check List<String> names = new ArrayList<>(); names.add("Alice"); names.add("Bob"); // Checking if "Alice" exists - time complexity O(n) if (names.contains("Alice")) { System.out.println("Found Alice"); } // Efficient - O(1) complexity for contains() check Set<String> namesSet = new HashSet<>(); namesSet.add("Alice"); namesSet.add("Bob"); // Checking if "Alice" exists - time complexity O(1) if (namesSet.contains("Alice")) { System.out.println("Found Alice"); }
In diesem Beispiel bietet HashSet eine durchschnittliche Zeitkomplexität von O(1) für contains()-Operationen, während ArrayList O(n) erfordert, da es die Liste durchlaufen muss. Daher ist ein HashSet für häufige Suchvorgänge effizienter als eine ArrayList.
Übrigens, wenn Sie wissen möchten, was Zeitkomplexität ist: Zeitkomplexität bezieht sich darauf, wie die Laufzeit eines Algorithmus mit der Eingabegröße variiert. Dies hilft uns zu verstehen, wie schnell ein Algorithmus läuft, und zeigt normalerweise, wie er sich im schlimmsten Fall verhält. Zeitkomplexität wird üblicherweise durch die Big-O-Notation bezeichnet.
Sie können unnötigen Verarbeitungsaufwand vermeiden, indem Sie zu Beginn der Methode die in der Methode zu verwendenden Felder überprüfen, die nicht null sein dürfen. Im Hinblick auf die Leistung ist es effektiver, sie zu Beginn zu überprüfen, anstatt in späteren Schritten der Methode nach Nullprüfungen oder illegalen Bedingungen zu suchen.
public void processOrder(Order order) { if (Objects.isNull(order)) throw new IllegalArgumentException("Order cannot be null"); if (order.getItems().isEmpty()) throw new IllegalStateException("Order must contain items"); ... // Process starts here. processItems(order.getItems()); }
Wie dieses Beispiel zeigt, kann die Methode andere Prozesse enthalten, bevor sie zur Methode „processItems“ gelangt. In jedem Fall ist die Items-Liste im Order-Objekt erforderlich, damit die Methode „processItems“ funktioniert. Sie können unnötige Bearbeitungen vermeiden, indem Sie die Bedingungen zu Beginn des Prozesses prüfen.
Das Erstellen unnötiger Objekte in Java-Anwendungen kann sich negativ auf die Leistung auswirken, indem die Garbage Collection-Zeit verlängert wird. Das wichtigste Beispiel hierfür ist die Verwendung von Strings.
Das liegt daran, dass die String-Klasse in Java unveränderlich ist. Dies bedeutet, dass jede neue String-Änderung ein neues Objekt im Speicher erstellt. Dies kann zu schwerwiegenden Leistungseinbußen führen, insbesondere in Schleifen oder wenn mehrere Verkettungen ausgeführt werden.
Der beste Weg, dieses Problem zu lösen, ist die Verwendung von StringBuilder. StringBuilder kann den String, an dem es arbeitet, ändern und Vorgänge für dasselbe Objekt ausführen, ohne jedes Mal ein neues Objekt zu erstellen, was zu einem effizienteren Ergebnis führt.
Im folgenden Codefragment wird beispielsweise für jeden Verkettungsvorgang ein neues String-Objekt erstellt:
// Inefficient - O(n) complexity for contains() check List<String> names = new ArrayList<>(); names.add("Alice"); names.add("Bob"); // Checking if "Alice" exists - time complexity O(n) if (names.contains("Alice")) { System.out.println("Found Alice"); } // Efficient - O(1) complexity for contains() check Set<String> namesSet = new HashSet<>(); namesSet.add("Alice"); namesSet.add("Bob"); // Checking if "Alice" exists - time complexity O(1) if (namesSet.contains("Alice")) { System.out.println("Found Alice"); }
In der Schleife oben wird mit jedem Ergebnis = Vorgang ein neues String-Objekt erstellt. Dies erhöht sowohl den Speicherverbrauch als auch die Verarbeitungszeit.
Wir können unnötige Objekterstellung vermeiden, indem wir dasselbe mit StringBuilder tun. StringBuilder verbessert die Leistung durch Ändern des vorhandenen Objekts:
public void processOrder(Order order) { if (Objects.isNull(order)) throw new IllegalArgumentException("Order cannot be null"); if (order.getItems().isEmpty()) throw new IllegalStateException("Order must contain items"); ... // Process starts here. processItems(order.getItems()); }
In diesem Beispiel wird mit StringBuilder nur ein Objekt erstellt und während der gesamten Schleife werden Operationen an diesem Objekt ausgeführt. Dadurch wird die Zeichenfolgenbearbeitung abgeschlossen, ohne dass neue Objekte im Speicher erstellt werden.
Die in der Java Stream API enthaltene flatMap-Funktion ist ein leistungsstarkes Tool zur Optimierung von Vorgängen für Sammlungen. Verschachtelte Schleifen können zu Leistungseinbußen und komplexerem Code führen. Durch die Verwendung dieser Methode können Sie Ihren Code lesbarer machen und die Leistung steigern.
map: Arbeitet an jedem Element und gibt als Ergebnis ein anderes Element zurück.
flatMap: Arbeitet an jedem Element, wandelt die Ergebnisse in eine flache Struktur um und stellt eine einfachere Datenstruktur bereit.
Im folgenden Beispiel werden Operationen mit Listen mithilfe einer verschachtelten Schleife ausgeführt. Je größer die Liste, desto ineffizienter wird der Vorgang.
String result = ""; for (int i = 0; i < 1000; i++) result += "number " + i;
Durch die Verwendung von flatMap können wir verschachtelte Schleifen loswerden und eine sauberere und leistungsfähigere Struktur erhalten.
StringBuilder result = new StringBuilder(); for (int i = 0; i < 1000; i++) result.append("number ").append(i); String finalResult = result.toString();
In diesem Beispiel konvertieren wir jede Liste mit flatMap in einen flachen Stream und verarbeiten die Elemente dann mit forEach. Diese Methode macht den Code sowohl kürzer als auch leistungseffizienter.
Die direkte Rückgabe von aus der Datenbank abgerufenen Daten als Entitätsklassen kann zu unnötiger Datenübertragung führen. Dies ist sowohl hinsichtlich der Sicherheit als auch der Leistung eine sehr fehlerhafte Methode. Stattdessen verbessert die Verwendung von DTO, um nur die benötigten Daten zurückzugeben, die API-Leistung und verhindert unnötig große Datenübertragungen.
List<List<String>> listOfLists = new ArrayList<>(); for (List<String> list : listOfLists) { for (String item : list) { System.out.println(item); } }
Die Datenbankleistung wirkt sich direkt auf die Geschwindigkeit der Anwendung aus. Die Leistungsoptimierung ist besonders wichtig, wenn Daten zwischen verwandten Tabellen abgerufen werden. An dieser Stelle können Sie unnötiges Laden von Daten vermeiden, indem Sie EntityGraph und Lazy Fetching verwenden. Gleichzeitig verbessert die ordnungsgemäße Indizierung bei Datenbankabfragen die Abfrageleistung erheblich.
Mit EntityGraph können Sie die zugehörigen Daten in Datenbankabfragen steuern. Sie rufen nur die Daten ab, die Sie benötigen, und vermeiden so die Kosten für Eager Fetching.
Eager Fetching ist, wenn die Daten in der zugehörigen Tabelle automatisch mit der Abfrage geliefert werden.
// Inefficient - O(n) complexity for contains() check List<String> names = new ArrayList<>(); names.add("Alice"); names.add("Bob"); // Checking if "Alice" exists - time complexity O(n) if (names.contains("Alice")) { System.out.println("Found Alice"); } // Efficient - O(1) complexity for contains() check Set<String> namesSet = new HashSet<>(); namesSet.add("Alice"); namesSet.add("Bob"); // Checking if "Alice" exists - time complexity O(1) if (namesSet.contains("Alice")) { System.out.println("Found Alice"); }
In diesem Beispiel werden adressbezogene Daten und Benutzerinformationen in derselben Abfrage abgerufen. Unnötige Zusatzabfragen werden vermieden.
Im Gegensatz zu Eager Fetch ruft Lazy Fetch nur bei Bedarf Daten aus verwandten Tabellen ab.
public void processOrder(Order order) { if (Objects.isNull(order)) throw new IllegalArgumentException("Order cannot be null"); if (order.getItems().isEmpty()) throw new IllegalStateException("Order must contain items"); ... // Process starts here. processItems(order.getItems()); }
Indizierung ist eine der effektivsten Methoden, um die Leistung von Datenbankabfragen zu verbessern. Eine Tabelle in einer Datenbank besteht aus Zeilen und Spalten. Bei einer Abfrage ist es häufig erforderlich, alle Zeilen zu scannen. Durch die Indizierung wird dieser Prozess beschleunigt, sodass die Datenbank ein bestimmtes Feld schneller durchsuchen kann.
Caching ist der Prozess der vorübergehenden Speicherung häufig aufgerufener Daten oder Rechenergebnisse in einem schnellen Speicherbereich wie dem Arbeitsspeicher. Ziel des Caching ist es, diese Informationen schneller bereitzustellen, wenn die Daten oder das Rechenergebnis erneut benötigt werden. Insbesondere bei Datenbankabfragen und Transaktionen mit hohem Rechenaufwand kann der Einsatz von Cache die Leistung deutlich verbessern.
Die @Cacheable-Annotation von Spring Boot macht die Cache-Nutzung sehr einfach.
String result = ""; for (int i = 0; i < 1000; i++) result += "number " + i;
In diesem Beispiel werden beim ersten Aufruf der Methode findUserById die Benutzerinformationen aus der Datenbank abgerufen und im Cache gespeichert. Wenn dieselben Benutzerinformationen erneut benötigt werden, werden sie aus dem Cache abgerufen, ohne in die Datenbank zu gehen.
Sie können für die Anforderungen Ihres Projekts auch eine erstklassige Caching-Lösung wie Redis verwenden.
Mit diesen Optimierungstechniken können Sie schnellere, effizientere und skalierbarere Anwendungen entwickeln, insbesondere in Ihren mit Java entwickelten Back-End-Projekten.
...
Vielen Dank für das Lesen meines Artikels! Wenn Sie Fragen, Feedback oder Gedanken haben, die Sie teilen möchten, würde ich mich freuen, diese in den Kommentaren zu hören.
Sie können mir auf Dev.to folgen, um weitere Informationen zu diesem Thema und meinen anderen Beiträgen zu erhalten. Vergessen Sie nicht, meine Beiträge zu liken, damit sie mehr Menschen erreichen!
Um mir auf LinkedIn zu folgen: tamerardal
Ressourcen:
Das obige ist der detaillierte Inhalt vonSteigern Sie die Leistung Ihres Java-Backends: Wichtige Optimierungstipps!. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!