subList与原始List相互影响

发布于:2024-12-18 ⋅ 阅读:(119) ⋅ 点赞:(0)

在 Java 中,List#subList(int fromIndex, int toIndex) 方法返回的是原始列表的一个视图(view),而不是一个独立的副本。这意味着对 subList 的任何修改都会反映到原始列表中,反之亦然。这可能会导致意外的行为,尤其是在你期望 subList 是一个独立的列表时。

示例:

package org.example.a;

import java.util.ArrayList;
import java.util.List;

public class Demo {
    public static void main(String[] args) {
        List<Integer> integerList = new ArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
        
        // 获取子列表
        List<Integer> subList = integerList.subList(0, 2);
        
        // 修改子列表中的元素
        subList.set(0, 10);
        // 修改原始列表中的元素
        integerList.set(1, 20);
        
        System.out.println("integerList: " + integerList);
        System.out.println("subList: " + subList);
    }
}

运行结果:

integerList: [10, 20, 3]

subList: [10, 20]

解释:

        修改 subList :当你调用 subList.set(0, 10) 时,实际上是在修改原始列表 integerList 的第一个元素,因此 integerList 的第一个元素变成了 10

        修改 integerList :当你调用 integerList.set(1, 20) 时,integerList 的第二个元素变成了 20,同时这个变化也会反映到 subList 中,因为 subList 只是 integerList 的一个视图。

源码

SubList 的工作原理:subList 方法

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

参数检查:首先调用 subListRangeCheck 方法确保索引范围有效。

返回 SubList 示例:然后创建并返回一个新的 SubList 对象,该对象持有对原始 ArrayList 的引用,并记录了子列表的起始和结束位置。

SubList 类

private class SubList extends AbstractList<E> implements RandomAccess {
    private final AbstractList<E> parent;
    private final int parentOffset;
    private final int offset;
    int size;

    SubList(AbstractList<E> parent,
            int offset, int fromIndex, int toIndex) {
        this.parent = parent;
        this.parentOffset = fromIndex;
        this.offset = offset + fromIndex;
        this.size = toIndex - fromIndex;
        this.modCount = ArrayList.this.modCount;
    }

    public void add(int index, E e) {
        rangeCheckForAdd(index);
        checkForComodification();
        parent.add(parentOffset + index, e);
        this.modCount = parent.modCount;
        this.size++;
    }
}

parent 字段:保存对原始 ArrayList 的引用。

parentOffset 和 offset 字段:记录子列表相对于原始列表的偏移量。

size 字段:表示子列表的大小。

add 方法:当向 SubList 添加元素时,实际上是通过 parent.add(parentOffset + index, e) 来操作原始列表。这意味着添加到 SubList 的元素会直接反映在原始列表中。

潜在问题

由于 SubList 实际上只是原始列表的一个视图,因此存在以下潜在问题:

        1. 相互影响:对 SubList 或原始列表的任何修改都会影响到对方,因为它们共享相同的底层数据结构。

        2. 内存泄漏:如果 SubList 被保留而原始列表很大,则会导致原始列表无法被垃圾回收,即使不再需要整个列表。这可能会导致内存溢出(OOM)问题,特别是在大量创建小 SubList 的情况下。

示例

private static List<List<Integer>> data = new ArrayList<>();

private static void oom() {
    for (int i = 0; i < 1000; i++) {
        List<Integer> rawList = IntStream.rangeClosed(1, 100000).boxed().collect(Collectors.toList());
        data.add(rawList.subList(0, 1));
    }
}

虽然 data 看起来只保存了 1000 个具有 1 个元素的子列表,但实际上每个 SubList 都强引用了整个 rawList,导致 GC 无法回收这些大列表,最终可能导致内存溢出(OOM)。

解决方案

        为了确保 subList 和原始列表之间没有相互影响,可以创建一个新的 ArrayList 来包含 subList 的内容。这样做会创建一个独立的副本,从而避免上述问题。

package org.example.a;

import java.util.ArrayList;
import java.util.List;

public class Demo {
    public static void main(String[] args) {
        List<Integer> integerList = new ArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
        
        // 创建新的 ArrayList 包含 subList 的内容
        List<Integer> subList = new ArrayList<>(integerList.subList(0, 2));
        
        // 修改子列表中的元素
        subList.set(0, 10);
        // 修改原始列表中的元素
        integerList.set(1, 20);
        
        System.out.println("integerList: " + integerList);
        System.out.println("subList: " + subList);
    }
}

运行结果:

integerList: [1, 20, 3]

subList: [10, 2]

解释:

        修改 subList :由于 subList 现在是一个独立的 ArrayList,对它的修改不会影响到原始列表 integerList

        修改 integerList :同样地,对 integerList 的修改也不会影响到 subList,因为它们现在是完全独立的。

总结

subList 是原始列表的一个视图:它不是独立的副本,而是原始列表的一部分。因此,任何对 subList 或原始列表的修改都会相互影响。

内存管理问题:由于 subList 强引用了原始列表,可能导致不必要的内存占用,甚至引发 内存溢出(OOM) 错误。

解决方案:如果你需要一个真正独立的子列表,应该使用 new ArrayList<>(list.subList(...)) 来创建一个新的 ArrayList 包含 subList 的内容。这将确保两个列表之间的操作不会相互影响,并且避免潜在的内存问题。


网站公告

今日签到

点亮在社区的每一天
去签到