避免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 一棵球