MST

星途 面试题库

面试题:Java Stream sorted自定义排序在复杂场景下的性能优化

假设存在一个非常大的Customer对象集合,Customer类包含多个属性如id(int)、registrationDate(LocalDate)、purchaseAmount(BigDecimal)等。现在需要使用Java Stream的sorted方法,按照registrationDate降序,purchaseAmount升序进行排序。在实现此功能的基础上,分析并阐述如何对该自定义排序操作进行性能优化,例如在大数据量下如何避免性能瓶颈,以及可能涉及到的算法和数据结构调整。请给出优化思路和关键代码片段。
15.3万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试
  1. 排序实现
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

class Customer {
    private int id;
    private LocalDate registrationDate;
    private BigDecimal purchaseAmount;

    public Customer(int id, LocalDate registrationDate, BigDecimal purchaseAmount) {
        this.id = id;
        this.registrationDate = registrationDate;
        this.purchaseAmount = purchaseAmount;
    }

    public int getId() {
        return id;
    }

    public LocalDate getRegistrationDate() {
        return registrationDate;
    }

    public BigDecimal getPurchaseAmount() {
        return purchaseAmount;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Customer> customers = List.of(
                new Customer(1, LocalDate.of(2023, 1, 1), new BigDecimal("100")),
                new Customer(2, LocalDate.of(2023, 2, 1), new BigDecimal("50")),
                new Customer(3, LocalDate.of(2023, 1, 1), new BigDecimal("150"))
        );

        List<Customer> sortedCustomers = customers.stream()
               .sorted(Comparator.comparing(Customer::getRegistrationDate).reversed()
                       .thenComparing(Customer::getPurchaseAmount))
               .collect(Collectors.toList());

        sortedCustomers.forEach(customer -> System.out.println(
                "Id: " + customer.getId() +
                        ", Registration Date: " + customer.getRegistrationDate() +
                        ", Purchase Amount: " + customer.getPurchaseAmount()
        ));
    }
}
  1. 性能优化思路
    • 减少比较次数
      • 提前过滤:在排序前,根据一些已知条件过滤掉不需要参与排序的数据。例如,如果已知大部分数据的registrationDate在某个时间段之后,可以先过滤掉不符合该时间段的数据。
      • 使用部分排序:如果只需要获取前n个排序后的元素,可以使用limit方法。Java Stream在这种情况下可能会采用更优化的策略,避免对整个数据集进行完全排序。
    • 选择合适的数据结构
      • 并行流:对于大数据量,可以考虑使用并行流parallelStream()。在多核CPU环境下,并行流能够利用多个线程并行处理数据,加快排序速度。但要注意,并行流存在线程安全和数据一致性问题,确保Customer对象的属性访问是线程安全的。
      • 外部排序:当数据量过大,内存无法容纳时,可以考虑外部排序算法。外部排序通常将数据分成多个部分,分别在内存中排序,然后再合并这些有序的部分。Java中可以通过java.util.PriorityQueue结合文件操作实现简单的外部排序。
    • 优化比较器
      • 缓存比较结果:如果Customer对象的属性计算比较复杂,例如registrationDatepurchaseAmount需要经过复杂计算得到,可以考虑缓存这些属性值,避免每次比较都重新计算。
  2. 关键代码片段 - 并行流优化
List<Customer> sortedCustomers = customers.parallelStream()
       .sorted(Comparator.comparing(Customer::getRegistrationDate).reversed()
               .thenComparing(Customer::getPurchaseAmount))
       .collect(Collectors.toList());
  1. 关键代码片段 - 外部排序示例(简化版)
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.PriorityQueue;

class ExternalSortCustomer {
    private int id;
    private LocalDate registrationDate;
    private BigDecimal purchaseAmount;

    public ExternalSortCustomer(int id, LocalDate registrationDate, BigDecimal purchaseAmount) {
        this.id = id;
        this.registrationDate = registrationDate;
        this.purchaseAmount = purchaseAmount;
    }

    public int getId() {
        return id;
    }

    public LocalDate getRegistrationDate() {
        return registrationDate;
    }

    public BigDecimal getPurchaseAmount() {
        return purchaseAmount;
    }

    @Override
    public String toString() {
        return "Id: " + id +
                ", Registration Date: " + registrationDate +
                ", Purchase Amount: " + purchaseAmount;
    }
}

public class ExternalSortExample {
    public static void main(String[] args) throws IOException {
        // 模拟大数据量,这里只简单举例
        PriorityQueue<ExternalSortCustomer> pq = new PriorityQueue<>(Comparator.comparing(ExternalSortCustomer::getRegistrationDate).reversed()
               .thenComparing(ExternalSortCustomer::getPurchaseAmount));
        pq.add(new ExternalSortCustomer(1, LocalDate.of(2023, 1, 1), new BigDecimal("100")));
        pq.add(new ExternalSortCustomer(2, LocalDate.of(2023, 2, 1), new BigDecimal("50")));
        pq.add(new ExternalSortCustomer(3, LocalDate.of(2023, 1, 1), new BigDecimal("150")));

        try (BufferedWriter writer = new BufferedWriter(new FileWriter("sorted_customers.txt"))) {
            while (!pq.isEmpty()) {
                writer.write(pq.poll().toString() + "\n");
            }
        }
    }
}