对具有百万或更多元素的ArrayList执行计算的最佳实践



我正在做一个面试测试,没有真正的方法来解决这个问题。我希望你们能帮我找出解决这类问题的最佳方法。

问题包括最大容量的ArrayList,即Integer.max_VALUE.

ArrayList<User> arr = new ArrayList<User>(Integer.MAX_VALUE);

这是假设,他们还提到

arr.ensureCapacity(Integer.MAX_VALUE); // shows no issues

据说User对象包含Int值a和b。

问题是,计算每个"n"的c值的最佳方法是什么;用户";其中c是a、b值相乘的结果。

我的答案是将列表分解成更小的列表,然后并行地迭代所有更小的列表。当我进行计算时,我会将结果添加到一个值为c的结果列表中

List<User> firstNElementsList = list.stream().limit(n).collect(Collectors.toList());

我不知道N的合适大小。我只是说N可以是任意的,比如1000、10000或100000。阶梯将有10个列表需要处理。

我考试不及格,所以这个答案还不够。有更好的主意吗?

使用并行流处理,为了保持轻量级,使用IntStream将结果收集为int[]:

int[] cs = arr.parallelStream().mapToInt(u -> u.getA() * u.getB()).toArray();

注意,当使用并行处理时,结果的顺序可能与输入的原始顺序不一致,但这并不是一项要求,只是为了";收集所有CCD_ 4";;它并不是说你必须知道c的每个值来自哪个User


尽管没有说明,或者abu.getA() * u.getB()的任意值可能会导致算术溢出,因此更安全的方法是使用long值作为结果:

long[] cs = arr.parallelStream().mapToLong(u -> u.getA() * u.getB()).toArray();

作为一名面试官,我希望候选人能要求澄清这一点,如果答案是";是";,并且如果有保证CCD_ 11从不溢出CCD_。

要解析的数据的大致大小为2^31*sizeof(User)。假设只有字段int A和int B,这大约是17.17GB。正如Bohemian所指出的,由于整数乘法可能会导致长,输出数组c的大小也大致相同,大小为2^31*8字节=17.17GB。

一些可能有用的注释包括:

  1. 每个用户都可以独立处理。例如:数据可以被分割成1GB的块,在几台机器上进行处理,然后进行聚合。类似地,每台机器上的数据集都可以并行处理
  2. 计算C的操作相对便宜。计算C的另一种选择是根据需要动态解析该值(这也将节省17GB的空间)。或者,如果C的计算成本很高,则可以在计算后缓存它,但只能根据需要进行计算

这个问题似乎是在问如何最好地计算每个用户的"c"值,而不仅仅是为每个用户计算"c"。这表明,问题可能在于如何在大型数据集中计算派生字段。因此,懒惰的方法可能是可以接受的,并且值得考虑(时间与空间的权衡)。

话虽如此,如果C需要同时预先计算,并且只有一台机器,那么使用并行流是一种合理的方法。在幕后,这将在机器上的多个核心中分配工作,非常适合计算操作。

在我看来,你的回答没有错,但有几个问题可能会导致面试官拒绝你的回答:

  1. 您说过要并行迭代列表的不同块,但您提供的是顺序代码List<User> firstNElementsList = list.stream().limit(n).collect(Collectors.toList());
  2. 计算完c后,您希望将结果添加到results列表中。这实际上会抵消您之前所做的并行化带来的好处。原因是并行线程的执行顺序是不可预测的,因此这些线程最终会处于竞争状态,试图修改同一资源(results列表),同时使资源处于不一致状态。这意味着,为了实现您所描述的,您需要在将c保存到results列表中时同步这些并行线程。有效地使整个事情再次按顺序进行,因为每个线程在修改results列表之前都需要等待上一个线程完成

在我看来,一种更快的方法是重用user对象,并在其中为c设置一个占位符,以便在计算后存储它。如果不允许,请创建包含c未来值占位符的对象的results列表。这样,在计算每个c之后,您还可以(并行地)从results列表中检索c的相应占位符并将其存储在那里。无需同步,因为您不是在修改results列表本身,而是在修改其中的单个对象

最新更新