如何在 Java 8 中对对象列表进行分页?

Posted

技术标签:

【中文标题】如何在 Java 8 中对对象列表进行分页?【英文标题】:How to paginate a list of objects in Java 8? 【发布时间】:2015-05-30 04:31:43 【问题描述】:

给定带有n 元素的java.util.List 和所需的页面大小m,我想将其转换为包含n/m+n%m 元素的地图。每个地图元素应包含m 元素。

这是一个整数示例:

    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    // What is the equivalent Java 8 code to create the map below from my list?

    Map<Integer, List<Integer>> map = new HashMap<>();
    map.put(0, Arrays.asList(1,2,3));
    map.put(1, Arrays.asList(4,5,6));
    map.put(2, Arrays.asList(7,8,9));
    map.put(3, Arrays.asList(10));

这可能吗,使用 Java 8?

【问题讨论】:

到目前为止你尝试过什么?请阅读How do I ask a good question?。 所以,我查看了 Collectors::partitioningBy ,但它拆分了一个给定谓词的列表。我问这个是因为我不知道在 Java 8 中从哪里开始来实现这一点。 @user3030447 确定要Map&lt;Integer,String&gt; 而不是Map&lt;Integer,List&lt;Integer&gt;&gt;?您始终可以在演示阶段将列表转换为通用字符串... 看this answer的结尾… @Alexis C.:由于集合到 Map 中,它不是完全重复的,但链接问题将有助于未来的搜索者。 【参考方案1】:

您可以将IntStream.iteratetoMap 收集器和List 上的subList 方法结合使用(感谢Duncan 的简化)。

import static java.util.stream.Collectors.toMap;
import static java.lang.Math.min;

...

static Map<Integer, List<Integer>> partition(List<Integer> list, int pageSize) 
    return IntStream.iterate(0, i -> i + pageSize)
          .limit((list.size() + pageSize - 1) / pageSize)
          .boxed()
          .collect(toMap(i -> i / pageSize,
                         i -> list.subList(i, min(i + pageSize, list.size()))));

您首先计算地图中所需的键数。这是由(list.size() + pageSize - 1) / pageSize 给出的(这将是流的限制)。

然后您创建一个 Stream 来创建序列 0, pageSize, 2* pageSize, ...

现在,对于每个值 i,您获取相应的 subList,这将是我们的值(您需要对最后一个 subList 进行额外检查,以免超出范围),您可以为其映射相应的键,这将是序列0/pageSize, pageSize/pageSize, 2*pageSize/pageSize 除以pageSize 得到自然序列0, 1, 2, ...

管道可以安全地并行运行(您可能需要改用toConcurrentMap 收集器)。正如 Brian Goetz 评论的(感谢您提醒我),如果您想并行化流,iterate 不值得,所以这里有一个带有range 的版本。

return IntStream.range(0, (list.size() + pageSize - 1) / pageSize)
                .boxed()
                .collect(toMap(i -> i ,
                               i -> list.subList(i * pageSize, min(pageSize * (i + 1), list.size()))));

与您的示例一样(包含 10 个元素的列表,页面大小为 3),您将获得以下序列:

0, 3, 6, 9, 12, 15, ... 限制为(10 + 3 - 1) / 3 = 12 / 3 = 4,这让序列0, 3, 6, 9。现在每个值都映射到其对应的子列表:

0 / pageSize = 0 -> list.subList(0, min(0 + pageSize, 10)) = list.subList(0, 3);
3 / pageSize = 1 -> list.subList(3, min(3 + pageSize, 10)) = list.subList(3, 6);
6 / pageSize = 2 -> list.subList(6, min(6 + pageSize, 10)) = list.subList(6, 9);
9 / pageSize = 3 -> list.subList(9, min(9 + pageSize, 10))  = list.subList(6, 10);
                                      ^
                                      |
                        this is the edge-case for the last sublist to
                        not be out of bounds


如果您真的想要Map&lt;Integer, String&gt;,您可以将值映射器函数替换为
import static java.util.stream.Collectors.joining;

...

i -> list.subList(i, min(i + pageSize, list.size()))
         .stream()
         .map(Object::toString)
         .collect(joining(","))

它只是将用逗号分隔的元素收集到一个字符串中。

【讨论】:

这是输出 0=[1, 2, 3], 1=[4, 5, 6], 2=[7, 8, 9], 3=[10]第一个版本。亚历克西斯,你是个 BLAST 宝贝,谢谢你给我看。我会理解代码并提高我的知识:) 祝你一切顺利 @user3030447 我在您的相应示例中添加了它的行为方式。 很好的解决方案。您可以将限制简化为:.limit((list.size() + pageSize - 1) / pageSize),因为这两个值都是正数(请参阅this answer)。值映射方法也可以简化为i -&gt; list.subList(i, Math.min(i + pageSize, list.size())) 您可以并行运行它,但是使用 IntStream.iterate() 启动管道会破坏您获得的任何并行性(这是一个基本的顺序源。)使用 IntStream.range 会更好,它确实与您的迭代相同,并且拆分得更好。那么你就不需要使用限制了,由于它对遇到顺序的基本依赖,它的并行性也很差。【参考方案2】:

使用 Guava 的简单解决方案:com.google.common.collect.Lists#partition:

    List<List<Integer>> partition = Lists.partition(list, 3); //<- here
    Map map = IntStream.range(0, partition.size()).boxed().collect(Collectors.toMap(
                    Function.identity(),
                    i -> partition.get(i)));

【讨论】:

【参考方案3】:

如 cmets 中所述,如果列表不是自然的整数序列,这也适用。您必须使用生成的IntStream 然后按索引引用列表中的元素。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

Map<Integer, String> map = IntStream
    .range(0, list.size())
    .boxed()
    .collect(groupingBy(
        i -> i / 3, //no longer i-1 because we start with 0
        mapping(i -> list.get((int) i).toString(), joining(","))
        ));

//result: 0="1,2,3", 1="4,5,6", 2="7,8,9", 3="10"

我们从代表列表索引的IntStream 开始。

groupingBy 通过一些分类器对元素进行分组。在您的情况下,它每页对 x 个元素进行分组。

mapping 将映射函数应用于元素并随后收集它们。映射是必要的,因为joining只接受CharSequencejoining 本身通过使用任意分隔符连接元素。

【讨论】:

如果您在列表中有一个自然序列,这很有效,但是假设列表 [1, -1, 5, 2] 和页面大小为 2,您将无法获得想要的结果。 @AlexisC。确实如此,但在这种情况下,您可以使用生成的IntStream 并按索引引用原始列表。不过算法不会改变。 @zeroflagL 您可以删除.limit(list.size()),因为range 已经生成了一个有限流:-)。此外,如果基础列表是 LinkedListget 会降低整体性能(尽管我不确定这是否是一个大问题)。 @AlexisC。当然。我以前用过iterate。谢谢。 @zeroflagL 与其编辑您的答案以指出第一部分已损坏,不如删除第一部分并向我们展示您的最佳解决方案。

以上是关于如何在 Java 8 中对对象列表进行分页?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 GatsbyJS 中对属于特定类别的帖子列表进行分页

如何在 Laravel 5 中对合并的集合进行分页?

如何在 codeIgniter 中对文件使用分页

如何在 Odoo 8 的 fom 视图中对树/列表视图进行分组?

如何在颤动中对对象列表中的json字符串进行编码

如何在laravel中对相关模型进行分页