歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Java集合的10個最常見問題

Java集合的10個最常見問題

日期:2017/3/1 9:14:46   编辑:Linux編程

以下是一些在Stackoverflow上經常被問起的與Java集合相關的問題。在你查閱這些問題之前,最好先去看看【Simple Java】Java集合框架的接口和類層次關系結構圖。

什麼時候優先選擇LinkedList,而不是ArrayList

ArrayList本質上是一個數組,它的元素可以直接通過索引訪問。但是,當數組滿的時候,需要申請新的更大的數組空間,並將所有元素復制到新數組中,這將花費O(n)的時間。另外,插入和刪除元素需要移動數組中的其它元素,這也許是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中,每個節點需要兩個額外的指針指向前節點和後節點,然而ArrayList只需要一個存放元素的數組即可。

其它List之間的比較,可查閱:【Simple Java】ArrayList vs LinkedList vs Vector。

迭代遍歷集合的時候,正確的刪除元素

在迭代集合的時候,唯一正確的方法修改集合的方法是通過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去實現這個功能,兩者都提供了filter()方法(Guava的Collections2或Apache的CollectionUtils類)。filter()方法會返回匹配的元素。

在JDK中,這將會變得困難,好消息是,在java 8中,增加了Predicate接口,可以實現該功能。但是現在,我們只能使用迭代器去遍歷整個集合:

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,這可能是高級開發人員才會做的事,如下:

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最簡單的方法

有兩種方法來實現該功能,取決於你如何定義“相等”。第一種方法將list存入HashSet,元素的重復主要由hashCode()來區分,大多數情況下,這是可行的。但是,如果你需要自己定義相等的比較方式,最好使用第二種方法,定義自己的比較器。

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來實現該功能。

對集合排序

Java中有幾種方式來維持集合中元素的順序,它們提供了默認的排序順序或者通過指定比較器來排序。不過即使是默認的排序,集合中的任何元素也需要實現Comparable接口。

  • Collections.sort()方法能夠對一個List集合進行排序,正如javadoc中描述的,這種排序方法是穩定且能保證排序性能為n log(n)
  • PriorityQueue為一個有序隊列,它與Collections.sort()的區別是PriorityQueue隊列一直是有序的,但是你只能訪問隊頭和隊尾,不能隨即訪問元素,如PriorityQueue.get(4)之類的操作。
  • 如果集合中沒有重復的元素,TreeSet是另外一種選擇。跟PriorityQueue類似,它能一直維護元素的順序,你能直接獲取TreeSet中的第一個和最後一個元素,但是你仍然不能隨即訪問集合中的元素。

簡單的說,Collections.sort()提供了對List的一次性排序,PriorityQueue和TreeSet能一直維持集合中元素的順序,但是不能隨即訪問元素。

Collections.emptyList()與直接new一個實例的區別

該問題也適用於emptyMap()和emptySet()。

這兩種方式都返回了一個空集合,但是Collections.emptyList()返回的是一個不可變集合,意味著你不能往這個空集合新增元素。事實上,每次調用Collections.emptyList()並不會創建一個空集合,而是復用已經存在的空集合實例。如果你熟悉單例模式��話,你應該知道我所說的,如果你頻繁調用的話,這將會提供更好的性能。

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);

這兩種方法都使用淺拷貝,那麼這兩種方法的區別是什麼呢?

  • 首先,當目標集合沒有足夠的空間存放源集合中的元素時,Collections.copy()方法不會對目標集合擴容,它會拋出一個IndexOutOfBoundsException異常。
  • Collections.copy()的參數類型只能是List接口的實現類,而ArrayList的構造方法可以接受Collection接口的實現類作為入參,因此更加普通。

譯文鏈接:http://www.programcreek.com/2013/09/top-10-questions-for-java-collections/

Copyright © Linux教程網 All Rights Reserved