subList 是分页首选,因其创建零拷贝逻辑视图、时间复杂度 O(1);但需规避索引左闭右开、结构性修改失效、Arrays.asList(基本类型数组)不适用三大陷阱。

用 subList 对 List 做分页,本质是创建一个不复制数据的逻辑视图,时间复杂度 O(1),内存零拷贝——但必须清楚它不是副本,而是原集合的“窗口”。
为什么 subList 是分页首选?
它不新建数组或遍历元素,仅维护起始偏移和长度,所有 get/set 操作都实时映射回原始 List 底层存储(如 ArrayList 的 elementData)。比如 100 万条数据的 List,取第 2 页(每页 50 条)只需:
list.subList(50, 100)
开销几乎为零,且后续修改该子列表会同步更新原数据。
必须避开的三个典型陷阱
- 索引是左闭右开区间:subList(2, 5) 取的是第 2、3、4 个元素(下标 2、3、4),不是 2 到 5 共 4 个;越界(如 toIndex > size)直接抛 IndexOutOfBoundsException
- 结构性修改会使所有已有 subList 失效:原 list 调用 add/remove 后,再访问之前创建的 subList 会触发 ConcurrentModificationException
- Arrays.asList(int[]) 不适用:基本类型数组会被整体当做一个元素,切片无效;必须用包装类(Integer[])或改用 IntBuffer.wrap(arr).slice()
安全分页的实用写法
分页场景中,通常只读不改,但仍需保障稳定性:
- 校验边界:分页前先判断 from ≤ to ≤ list.size(),避免运行时异常
- 只读遍历优先用 for-each:
for (Item item : list.subList(from, to)) { ... } - 若需独立副本(如跨线程传递或防误改),明确构造:
new ArrayList(list.subList(from, to)) —— 此时才真正复制 - 对超大 ArrayList 分页后若要长期持有子视图,建议立即转成新 ArrayList 并指定容量,避免后续扩容干扰
替代方案对比(何时不用 subList)
当不满足 subList 使用前提时,可考虑:
- 原始数据是 int[]/double[] 等基本类型数组 → 用 IntBuffer.wrap(arr).position(from).limit(to) 或手动索引遍历
- 需要线程安全的分页视图 → 包装为 Collections.unmodifiableList(list.subList(...)),或用 CopyOnWriteArrayList(注意写多场景开销)
- 高频数值计算 + 复杂切片(如步长、反转)→ 引入 FastUtil 或 Eclipse Collections 提供的原生类型视图类

















