效能對於軟體專案的成功起著至關重要的作用。 Java 後端開發過程中應用的最佳化可確保系統資源的有效利用並提高應用程式的可擴展性。
在本文中,我將與您分享一些我認為很重要的最佳化技巧,以避免常見錯誤。
選擇高效的資料結構可以顯著提高應用程式的效能,尤其是在處理大型資料集或時間關鍵型操作時。使用正確的資料結構可以最大限度地減少存取時間、優化記憶體使用並減少處理時間。
例如,當您需要在清單中頻繁搜尋時,使用 HashSet 而不是 ArrayList 可以產生更快的結果:
// 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"); }
在此範例中,HashSet 為 contains() 運算提供的平均時間複雜度為 O(1),而 ArrayList 需要 O(n),因為它必須迭代列表。因此,對於頻繁查找,HashSet 比 ArrayList 更有效率。
順便說一下,如果你想知道什麼是時間複雜度:時間複雜度是指演算法的運行時間如何隨著輸入大小的變化而變化。這有助於我們了解演算法的運行速度,並通常顯示它在最壞情況下的行為。時間複雜度通常以大 O 表示法表示。
您可以透過檢查方法中要使用的欄位(在方法開頭不應為空)來避免不必要的處理開銷。就性能而言,在開始時檢查它們比在方法的後續步驟中檢查空檢查或非法條件更有效。
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()); }
如本例所示,在進入 processItems 方法之前,該方法可能包含其他流程。在任何情況下,processItems 方法都需要 Order 物件中的 Items 清單才能運作。您可以透過在流程開始時檢查條件來避免不必要的處理。
在 Java 應用程式中建立不必要的物件可能會增加垃圾收集時間,從而對效能產生負面影響。最重要的例子是字串的使用。
這是因為Java中的String類別是不可變的。這意味著每次新的 String 修改都會在記憶體中建立一個新物件。這可能會導致嚴重的效能損失,尤其是在循環中或執行多個串聯時。
解決這個問題的最好方法是使用StringBuilder。 StringBuilder 可以修改它正在處理的 String 並在同一物件上執行操作,而無需每次都建立新對象,從而獲得更有效率的結果。
例如,在下面的程式碼片段中,為每個串聯操作建立一個新的 String 物件:
// 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"); }
在上面的循環中,每個 result = 操作都會建立一個新的 String 物件。這會增加記憶體消耗和處理時間。
我們可以透過對 StringBuilder 執行相同的操作來避免不必要的物件建立。 StringBuilder 透過修改現有物件來提高效能:
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()); }
在這個例子中,使用StringBuilder只建立了一個對象,並在整個循環中對該對象進行操作。這樣就完成了字串操作,而無需在記憶體中建立新物件。
Java Stream API 中包含的 flatMap 函數是最佳化集合操作的強大工具。嵌套循環可能會導致效能損失和更複雜的程式碼。透過使用這種方法,您可以使程式碼更具可讀性並提高效能。
map: 對每個元素進行操作並傳回另一個元素作為結果。
flatMap:對每個元素進行操作,將結果轉換為平面結構,提供更簡單的資料結構。
在下列範例中,使用巢狀循環對清單執行操作。隨著清單的擴大,操作會變得更加低效。
String result = ""; for (int i = 0; i < 1000; i++) result += "number " + i;
透過使用 flatMap,我們可以擺脫巢狀循環並獲得更乾淨、效能更高的結構。
StringBuilder result = new StringBuilder(); for (int i = 0; i < 1000; i++) result.append("number ").append(i); String finalResult = result.toString();
在這個範例中,我們使用 flatMap 將每個清單轉換為扁平流,然後使用 forEach 處理元素。這種方法使程式碼更短並且效能更有效率。
直接傳回從資料庫中提取的資料作為實體類別可能會導致不必要的資料傳輸。無論從安全性還是效能角度來看,這都是一種非常錯誤的方法。相反,使用 DTO 僅返回所需的資料可以提高 API 效能並防止不必要的大數據傳輸。
List<List<String>> listOfLists = new ArrayList<>(); for (List<String> list : listOfLists) { for (String item : list) { System.out.println(item); } }
資料庫效能直接影響應用程式的速度。在相關表格之間檢索資料時,效能最佳化尤其重要。此時,您可以透過使用 EntityGraph 和 Lazy Fetching 來避免不必要的資料載入。同時,資料庫查詢中正確的索引可以大幅提高查詢效能。
EntityGraph 可讓您控制資料庫查詢中的關聯資料。您只提取所需的數據,避免急切獲取的成本。
Eager Fetching 是指相關表格中的資料會自動隨查詢而來。
// 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"); }
在此範例中,在同一查詢中檢索地址相關資料和使用者資訊。避免不必要的額外查詢。
與 Eager Fetch 不同,Lazy Fetch 僅在需要時才從相關表格中取得資料。
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()); }
索引是提高資料庫查詢效能最有效的方法之一。資料庫中的表格由行和列組成,在進行查詢時,常常需要掃描所有行。索引加快了這一過程,使資料庫能夠更快地搜尋特定欄位。
快取是將經常存取的資料或計算結果暫時儲存在快速儲存區域(例如記憶體)中的過程。快取的目的是當再次需要資料或計算結果時更快地提供這些資訊。尤其是在計算成本較高的資料庫查詢和事務中,使用快取可以顯著提高效能。
Spring Boot 的 @Cacheable 註解讓快取的使用變得非常簡單。
String result = ""; for (int i = 0; i < 1000; i++) result += "number " + i;
在此範例中,當第一次呼叫 findUserById 方法時,將從資料庫中檢索使用者資訊並將其儲存在快取中。當再次需要相同的使用者資訊時,會從快取中檢索,而不去資料庫。
您也可以根據專案的需要使用Redis等頂級快取解決方案。
您可以使用這些最佳化技術開發更快、更有效率且可擴展的應用程序,特別是在使用 Java 開發的後端專案中。
...
謝謝您閱讀我的文章!如果您有任何問題、反饋或想法想要分享,我很樂意在評論中聽到它們。
您可以在 Dev.to 上關注我,以獲取有關此主題和我的其他帖子的更多資訊。不要忘記喜歡我的帖子,以幫助他們接觸到更多人!
在 LinkedIn 上追蹤我:tamerardal
資源:
以上是提升您的 Java 後端效能:基本的優化技巧!的詳細內容。更多資訊請關注PHP中文網其他相關文章!