首页  >  文章  >  Java  >  提升您的 Java 后端性能:基本的优化技巧!

提升您的 Java 后端性能:基本的优化技巧!

Mary-Kate Olsen
Mary-Kate Olsen原创
2024-11-11 21:56:03370浏览

Boost Your Java Back-End Performance: Essential Optimization Tips!

性能对于软件项目的成功起着至关重要的作用。 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只创建了一个对象,并在整个循环中对该对象进行操作。这样就完成了字符串操作,而无需在内存中创建新对象。

告别嵌套循环:使用 Flatmap

Java Stream API 中包含的 flatMap 函数是优化集合操作的强大工具。嵌套循环可能会导致性能损失和更复杂的代码。通过使用这种方法,您可以使代码更具可读性并提高性能。

  • 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 而不是实体

直接返回从数据库中提取的数据作为实体类可能会导致不必要的数据传输。无论从安全性还是性能角度来看,这都是一种非常错误的方法。相反,使用 DTO 仅返回所需的数据可以提高 API 性能并防止不必要的大数据传输。

List<List<String>> listOfLists = new ArrayList<>();
for (List<String> list : listOfLists) {
    for (String item : list) {
        System.out.println(item);
    }
}

说到数据库;使用 EntityGraph、索引和延迟获取功能

数据库性能直接影响应用程序的速度。在相关表之间检索数据时,性能优化尤其重要。此时,您可以通过使用 EntityGraph 和 Lazy Fetching 来避免不必要的数据加载。同时,数据库查询中正确的索引可以极大地提高查询性能。

  • 使用 EntityGraph 优化数据提取

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

资源:

  1. Baeldung,JPA 实体图
  2. Baeldung,Spring 缓存指南
  3. Baeldung,Hibernate 中的急切/延迟加载

以上是提升您的 Java 后端性能:基本的优化技巧!的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn