避免subList/subString陷阱
java.util.List
接口提供了一个实例方法List<E> subList(int fromIndex, int toIndex)
, 用于截取 原List 的一个以 fromIndex 为起始索引,以 toIndex 为终止索引(不包括)的子列表。- subString 类似于 subList ,对 String 进行截取。
subList/subString陷阱
subString在本文涉及到的方面都类似于subList,不再赘述
关于函数返回值使用的陷阱
subList的实现代码在AbstractList类里,返回AbstractList的一个子类SubList
class SubList<E> extends AbstractList<E>{
private AbstractList<E> l;
private int offset;
private int size;
//SubList类的构造方法
SubList(AbstractList list, int fromIndex, int toIndex){
//...
l = list;
offset = fromIndex;
size = toIndex - fromIndex;
//...
}
//...
根据以上代码,我们知道,SubList将原来的list赋值给了l,只是改变了偏移量offset。也就是说,subList函数返回了原始list对象的引用。
这也就意味着,对原list和返回的list做的“非结构性修改”(指不涉及list大小改变的修改),都会影响到彼此。
//对返回的list排序,观察原list
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
List<Integer> list = new ArrayList<>();
while(sc.hasNextInt())
{
list.add(sc.nextInt());
}
List<Integer> subList = new ArrayList<>();
subList = list.subList(0, list.size());
subList.sort(null);
for(int i = 0; i < list.size(); i++)
System.out.print(list.get(i) + " ");
sc.close();
}
//输入:
1 3 2 5 4
//输出:
1 2 3 4 5
OOM问题
看到这里读者可能会产生疑问,为什么要用原list的引用作为返回值?
原因在于,这种实现可以提高运行时的性能;
如果创建一个新的list,然后添加所需的内容,这种实现方法无论从内存消耗还是从运行效率来看都远不如直接引用原始list、设置offset和size的方法。
然而,这种返回引用的方法可能会引起一个更加麻烦且难发现的错误:out of memory。
在SubList中,保存了原list的强引用,这意味着,在jvm进行垃圾回收时,只要有一个这样的SubList没有被回收,原List就不会被回收,从而占用这内存空间。当我们只需要长时间保留原List中的一小段数据,却用了subList,而除了SubList中的引用之外,没有其他的引用指向原List,这种情况下,就会造成严重的内存空间的浪费。
可想而知,当这样的subList足够多,jvm没办法及时回收,这些subList就会吃掉所有内存。
如何避免陷阱
正确使用subString/subList
-
subList/subString适合处理局部的List/String时,比如删除局部的List :
list.subList(begin, end).clear();
subList/subString的一个明显优势在于提高了运行的性能,不用进行冗长的复制过程。所以,如果对程序的性能要求高,可以考虑用subString/subList。
避免使用subList/subString
如果有截取整体中较小的一段来做操作而不改变原整体的需要,或有截取一小段长期保存在内存中的需要
-
以subList为例:
//...
List<Integer> newList = new ArrayList<>();
for(int i = begin; i < end; i++)
{
//原List为list
newList.add(list.get(i));
}
//... 另外,对于String,可以用
new String()
来构造一个新的String来复制需要的部分。
update by 2017/3/30 19:33
by 一棵球