list分页
处理几万条数据的 List 分页,关键在于平衡性能、内存占用和开发效率。下面我用一个表格为你总结几种主流方法的优缺点,帮你快速决策:
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
| 性能极佳 (返回原列表视图,不创建新集合) | 分页结果与原列表绑定,原列表修改会影响分页结果 | 需要最高性能、且能接受分页结果与源数据联动的大数据量分页 |
Stream API | 函数式风格,代码简洁,可与流操作链式组合 | 会创建新列表,有一定内存开销 | 适合在分页前后需要进行复杂数据操作(如过滤、映射)的场景 |
Apache Commons | 代码简洁,一次分块可多次取页,生成真实副本,数据隔离性好 | 需引入 | 需要数据隔离或已使用该库的其他功能时 |
Google Guava | 与 Apache Commons 类似,提供更丰富的集合操作功能 | 需引入 | 项目已使用 Guava 或需要其更多集合工具时 |
🛠️ 代码示例与说明
1. 使用 subList()
(性能最佳)
public static <T> List<T> paginateBySubList(List<T> sourceList, int pageNum, int pageSize) {if (sourceList == null || sourceList.isEmpty()) {return Collections.emptyList();}int totalItems = sourceList.size();int fromIndex = (pageNum - 1) * pageSize;if (fromIndex >= totalItems) {return Collections.emptyList(); // 请求页码超出范围,返回空列表}int toIndex = Math.min(fromIndex + pageSize, totalItems);return sourceList.subList(fromIndex, toIndex);
}
关键说明:subList()
返回的是原列表的一个视图。这意味着:
优点:性能极高,因为不会复制数据,只是包装了一个偏移量。
注意:如果你不希望分页的结果随着原列表的修改而改变,或者原列表可能被修改导致分页结果失效,你需要创建它的副本:
return new ArrayList<>(sourceList.subList(fromIndex, toIndex));
。
2. 使用 Stream API(函数式风格)
public static <T> List<T> paginateByStream(List<T> sourceList, int pageNum, int pageSize) {if (sourceList == null || sourceList.isEmpty()) {return Collections.emptyList();}return sourceList.stream().skip((pageNum - 1) * pageSize).limit(pageSize).collect(Collectors.toList());
}
关键说明:这种方式会创建新的列表对象,因此内存开销比 subList()
大,但代码非常清晰,尤其适合在分页前后进行过滤、映射等流操作。
3. 使用 Apache Commons Collections
// 首先需要引入依赖:org.apache.commons:commons-collections4
import org.apache.commons.collections4.ListUtils;public static <T> List<T> paginateByApacheCommons(List<T> sourceList, int pageNum, int pageSize) {if (sourceList == null || sourceList.isEmpty()) {return Collections.emptyList();}List<List<T>> partitions = ListUtils.partition(sourceList, pageSize); // 先按页大小分割if (pageNum < 1 || pageNum > partitions.size()) {return Collections.emptyList();}return partitions.get(pageNum - 1);
}
关键说明: ListUtils.partition
会先将列表按指定大小分块,返回一个包含各分块视图的列表。获取某一页时,get
方法会创建该分块的新列表副本,因此返回的数据与原列表隔离,不受修改影响。
4. 使用 Google Guava
// 首先需要引入依赖:com.google.guava:guava
import com.google.common.collect.Lists;public static <T> List<T> paginateByGuava(List<T> sourceList, int pageNum, int pageSize) {if (sourceList == null || sourceList.isEmpty()) {return Collections.emptyList();}List<List<T>> partitions = Lists.partition(sourceList, pageSize);if (pageNum < 1 || pageNum > partitions.size()) {return Collections.emptyList();}return partitions.get(pageNum - 1);
}
关键说明: 其原理和用法与 Apache Commons 非常相似,同样会生成数据隔离的分页结果。如果你的项目已经大量使用 Guava,这是不错的选择。
⚠️ 重要提醒与优化建议
处理几万条数据时,以下几点至关重要:
数据来源是根本:如果这些数据来自数据库,强烈建议直接在数据库查询时进行分页(如 SQL 的
LIMIT ... OFFSET
、ROW_NUMBER()
或 MyBatis PageHelper 等),而不是将所有数据读到内存中再分页。这是处理大数据集最有效的方法。内存警告:将几万条数据全部加载到内存中本身就有内存溢出(OOM)的风险。请务必评估你的列表大小和JVM内存设置。
边界处理:所有示例中都包含了对空列表、页码越界的检查,这是生产代码中必不可少的。
页码惯例:通常页码 (
pageNum
) 从 1 开始计算。考虑更优分页策略:对于超大数据集,传统的
LIMIT ... OFFSET
在深度分页时性能会下降。可以考虑基于游标(Cursor) 或上次检索到的最大ID的分页方式(例如WHERE id > ? ORDER BY id LIMIT ?
),性能更稳定。
💡 如何选择?
追求极致性能,且能接受分页视图与原列表的关联:选
subList()
。需要在分页流程中集成复杂的流式操作:选 Stream API。
希望代码简洁,且需要分页结果与原列表数据隔离:选择 Apache Commons 或 Google Guava。两者类似,取决于你的项目依赖偏好。
最重要的一点:如果数据源自数据库,务必在SQL查询层面进行分页,这是最根本的优化。
希望这些信息能帮助你为几万条数据选择合适的分页方法!