以下是一些在Stackoverflow上經常被問起的與Java語言集合相關的問題。
什么時候優(yōu)先選擇LinkedList,而不是ArrayList
ArrayList本質上是一個數(shù)組,它的元素可以直接通過索引訪問。但是,當數(shù)組滿的時候,需要申請新的更大的數(shù)組空間,并將所有元素復制到新數(shù)組中,這將花費O(n)的時間。另外,插入和刪除元素需要移動數(shù)組中的其它元素,這也許是ArrayList最大的劣勢。
LinkedList是一個雙向鏈表,因此,當訪問鏈表中間的元素的時候,它需要從鏈表的頭結點處開始搜索。但好的一方面是,對于插入和刪除操作,LinkedList相對更快,因為它只需要改變鏈表的局部位置。
總的來說,在最差情況下,兩者的時間復雜度比較如下:
| Arraylist | LinkedList
------------------------------------------
get(index) | O(1) | O(n)
add(E) | O(n) | O(1)
add(E, index) | O(n) | O(n)
remove(index) | O(n) | O(n)
Iterator.remove() | O(n) | O(1)
Iterator.add(E) | O(n) | O(1)
除了運行時間外,內存空間的使用也應該被考慮,特別是對于大的列表。在LinkedList中,每個節(jié)點需要兩個額外的指針指向前節(jié)點和后節(jié)點,然而ArrayList只需要一個存放元素的數(shù)組即可。
迭代遍歷集合的時候,正確的刪除元素
在迭代集合的時候,唯一正確的方法修改集合的方法是通過Iterator.remove()方法,如下示例:
Iterator<Integer> itr = list.iterator();while(itr.hasNext()) {
// do something itr.remove();
}
另外,舉一個常見的錯誤代碼:
1 2 3 |
for(Integer i: list) { list.remove(i); } |
運行以上錯誤代碼,你將會得到ConcurrentModificationException異常,這是因為以上代碼產生了一個迭代器(由for語句產生)去遍歷列表,但是同時,列表被修改了(通過Iterator.remove())。在Java中,一個線程在迭代遍歷集合的過程中,是不允許另外一個線程去修改集合的。
怎樣將List轉成int[]
最簡單的方法是使用Apache Commons Lang庫下的ArrayUtils工具類,如下:
int[] array = ArrayUtils.toPrimitive(list.toArray(new Integer[0]));
在JDK中,沒有捷徑去做以上轉換。注意你不能使用List.toArray()方法,因為這將會把List轉成Integer[]。正確的方法如下:
int[] array = new int[list.size()];for(int i=0; i < list.size(); i++) {
array[i] = list.get(i);
}
怎樣將int[]轉成List
最簡單的方法仍然是使用Apache Commons Lang的ArrayUtils工具類,如下:
List list = Arrays.asList(ArrayUtils.toObject(array));
在JDK中,仍然沒有捷徑,只能使用如下方法:
int[] array = {1,2,3,4,5};
List<Integer> list = new ArrayList<Integer>();for(int i: array) {
list.add(i);
}
什么是過濾集合最好的方法
同樣,你可以使用第三方庫,如google的Guava庫或Apache Commons Lang去實現(xiàn)這個功能,兩者都提供了filter()方法(Guava的Collections2或Apache的CollectionUtils類)。filter()方法會返回匹配的元素。
在JDK中,這將會變得困難,好消息是,在java 8中,增加了Predicate接口,可以實現(xiàn)該功能。但是現(xiàn)在,我們只能使用迭代器去遍歷整個集合:
Iterator<Integer> itr = list.iterator();while(itr.hasNext()) {
int i = itr.next();
if (i > 5) { // filter all ints bigger than 5
itr.remove();
}
}
當然,你可以模仿Guava或Apache的操作方法,通過引入一個新的接口Predicate,這可能是高級開發(fā)人員才會做的事,如下:
public interface Predicate<T> {
boolean test(T o);
}public static <T> void filter(Collection<T> collection, Predicate<T> predicate) {
if ((collection != null) && (predicate != null)) {
Iterator<T> itr = collection.iterator();
while(itr.hasNext()) {
T obj = itr.next();
if (!predicate.test(obj)) {
itr.remove();
}
}
}
}
然后,我們使用如下代碼去過濾集合:
filter(list, new Predicate<Integer>() {
public boolean test(Integer i) {
return i <= 5;
}
});
List轉Set最簡單的方法
有兩種方法來實現(xiàn)該功能,取決于你如何定義“相等”。第一種方法將list存入HashSet,元素的重復主要由hashCode()來區(qū)分,大多數(shù)情況下,這是可行的。但是,如果你需要自己定義相等的比較方式,最好使用第二種方法,定義自己的比較器。
Set<Integer> set = new HashSet<Integer>(list);
Set<Integer> set = new TreeSet<Integer>(aComparator);
set.addAll(list);
ArrayList中刪除重復元素
這個問題和上一個很像,如果你不關心ArrayList中元素的順序的話,一個聰明的方法是通過將list元素存入set集合中來去除重復元素,然后將set集合的元素移回到List中。如下代碼:
ArrayList** list = ... // initial a list with duplicate elements
Set<Integer> set = new HashSet<Integer>(list);
list.clear();
list.addAll(set);
如果你關心元素的順序的話,可以使用標準JDK中的LinkedHashSet來實現(xiàn)該功能。
對集合排序
Java中有幾種方式來維持集合中元素的順序,它們提供了默認的排序順序或者通過指定比較器來排序。不過即使是默認的排序,集合中的任何元素也需要實現(xiàn)Comparable接口。
Collections.sort()方法能夠對一個List集合進行排序,正如javadoc中描述的,這種排序方法是穩(wěn)定且能保證排序性能為n log(n)。
PriorityQueue為一個有序隊列,它與Collections.sort()的區(qū)別是PriorityQueue隊列一直是有序的,但是你只能訪問隊頭和隊尾,不能隨即訪問元素,如PriorityQueue.get(4)之類的操作。
如果集合中沒有重復的元素,TreeSet是另外一種選擇。跟PriorityQueue類似,它能一直維護元素的順序,你能直接獲取TreeSet中的第一個和最后一個元素,但是你仍然不能隨即訪問集合中的元素。
簡單的說,Collections.sort()提供了對List的一次性排序,PriorityQueue和TreeSet能一直維持集合中元素的順序,但是不能隨即訪問元素。
Collections.emptyList()與直接new一個實例的區(qū)別
該問題也適用于emptyMap()和emptySet()。
這兩種方式都返回了一個空集合,但是Collections.emptyList()返回的是???個不可變集合,意味著你不能往這個空集合新增元素。事實上,每次調用Collections.emptyList()并不會創(chuàng)建一個空集合,而是復用已經存在的空集合實例。如果你熟悉單例模式的話,你應該知道我所說的,如果你頻繁調用的話,這將會提供更好的性能。
Collections.copy方法
有兩種方法講一個List集合拷貝到另外一個List集合,其中一種是使用ArrayList的構造方法,如下:
ArrayList<Integer> dstList = new ArrayList<Integer>(srcList);
另一種是使用Collections.copy()方法(如下),注意第一行,我們分配了一個和源List集合長度相等的初始容量。
ArrayList<Integer> dstList = new ArrayList<Integer>(srcList.size());
Collections.copy(dstList, srcList);
這兩種方法都使用淺拷貝,那么這兩種方法的區(qū)別是什么呢?
首先,當目標集合沒有足夠的空間存放源集合中的元素時,Collections.copy()方法不會對目標集合擴容,它會拋出一個IndexOutOfBoundsException異常。
Collections.copy()的參數(shù)類型只能是List接口的實現(xiàn)類,而ArrayList的構造方法可以接受Collection接口的實現(xiàn)類作為入?yún)?,因此更加普通?/span>
原文來自:Linux公社