天天看點

Java集合類詳解Java集合類詳解Collection接口 List接口Map接口 Set接口Queue接口總結互相差別Iterator(疊代器)的用法

Java集合類詳解

Java集合類詳解Java集合類詳解Collection接口 List接口Map接口 Set接口Queue接口總結互相差別Iterator(疊代器)的用法

Collection接口

        Collection是最基本的集合接口,一個Collection代表一組Object,即Collection的元素(Elements)。一些 Collection允許相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接繼承自Collection的類,Java SDK提供的類都是繼承自Collection的“子接口”如List和Set。

        所有實作Collection接口的類都必須提供兩個标準的構造函數:無參數的構造函數用于建立一個空的Collection,有一個 Collection參數的構造函數用于建立一個新的Collection,這個新的Collection與傳入的Collection有相同的元素。後一個構造函數允許使用者複制一個Collection。

        如何周遊Collection中的每一個元素?不論Collection的實際類型如何,它都支援一個iterator()的方法,該方法傳回一個疊代子,使用該疊代子即可逐一通路Collection中每一個元素。典型的用法如下:

        Iterator it = collection.iterator(); //獲得一個疊代子

                while(it.hasNext()) {

        Objectobj = it.next(); //得到下一個元素

        }

        由Collection接口派生的兩個接口是List和Set。

List接口

        List是有序的Collection,使用此接口能夠精确的控制每個元素插入的位置。使用者能夠使用索引(元素在List中的位置,類似于數組下标)來通路List中的元素,這類似于Java的數組。

        實際上有兩種List:一種是基本的ArrayList其優點在于随機通路元素,另一種是更強大的LinkedList它并不是為快速随機通路設計的,而是具有一套更通用的方法。

        和下面要提到的Set不同,List允許有相同的元素。

        除了具有Collection接口必備的iterator()方法外,List還提供一個listIterator()方法,傳回一個ListIterator接口,和标準的Iterator接口相比,ListIterator多了一些add()之類的方法,允許添加,删除,設定元素,還能向前或向後周遊。

實作List接口的常用類有LinkedList,ArrayList,Vector和Stack。

LinkedList類

        LinkedList實作了List接口,允許null元素。對順序通路進行了優化,向List中間插入與删除的開銷并不大。随機通路則相對較慢。還具有下列方法:addFirst(),addLast(),getFirst(),getLast(),removeFirst()和removeLast()這些方法(沒有在任何接口或基類中定義過),這些操作使LinkedList可被用作堆棧(stack),隊列(queue)或雙向隊列(deque)。

        注意LinkedList沒有同步方法。如果多個線程同時通路一個List,則必須自己實作通路同步。一種解決方法是在建立List時構造一個同步的List:

        List list = Collections.synchronizedList(newLinkedList(...));

ArrayList類

        ArrayList實作了可變大小的數組。它允許所有元素,包括null。ArrayList沒有同步。允許對元素進行快速随機通路,但是向List中間插入與移除元素的速率很慢。

        size,isEmpty,get,set方法運作時間為常數。但是add方法開銷為分攤的常數,添加n個元素需要O(n)的時間。其他的方法運作時間為線性。

        每個ArrayList執行個體都有一個容量(Capacity),即用于存儲元素的數組的大小。這個容量可随着不斷添加新元素而自動增加,但是增長算法并沒有定義。當需要插入大量元素時,在插入前可以調用ensureCapacity方法來增加ArrayList的容量以提高插入效率。

        和LinkedList一樣,ArrayList也是非同步的(unsynchronized)。

Vector類

        Vector非常類似ArrayList,但是Vector是同步的。由Vector建立的Iterator,雖然和 ArrayList建立的Iterator是同一接口,但是,因為Vector是同步的,當一個Iterator被建立而且正在被使用,另一個線程改變了Vector的狀态(例如,添加或删除了一些元素),這時調用Iterator的方法時将抛出ConcurrentModificationException,是以必須捕獲該異常。

Stack類

        Stack繼承自Vector,實作一個後進先出的堆棧。Stack提供5個額外的方法使得Vector得以被當作堆棧使用。基本的push和pop方法,還有peek方法得到棧頂的元素,empty方法測試堆棧是否為空,search方法檢測一個元素在堆棧中的位置。Stack剛建立後是空棧。

Map接口

        請注意,Map沒有繼承Collection接口,Map提供key到value的映射。一個Map中不能包含相同的key,每個key隻能映射一個value。Map接口提供3種集合的視圖,Map的内容可以被當作一組key集合,一組value集合,或者一組key-value映射。

        方法put(Object key, Object value)添加一個"值"(想要得東西)和與"值"相關的"鍵"(key)(使用它來查找)。方法get(Object key)傳回與給定"鍵"相關聯的"值"。可以用containsKey()和containsValue()測試Map中是否包含某個"鍵"或"值"。

Hashtable類

        Hashtable繼承Map接口,實作一個key-value映射的哈希表。任何非空(non-null)的對象都可作為key或者value。

        添加資料使用put(key, value),取出資料使用get(key),這兩個基本操作使用Java的hashCode來實作,時間開銷為常數。

        Hashtable通過initial capacity(初始容量)和load factor(加載因子)兩個參數調整性能。通常預設的load factor為0.75較好地實作了時間和空間的均衡。增大load factor可以節省空間但相應的查找時間将增大,這會影響像get和put這樣的操作。

        使用Hashtable的簡單示例如下,将1,2,3放到Hashtable中,他們的key分别是”one”,”two”,”three”:

        Hashtable numbers = new Hashtable();

        numbers.put(“one”, new Integer(1));

        numbers.put(“two”, new Integer(2));

        numbers.put(“three”, new Integer(3));

        要取出一個數,比如2,用相應的key:

        Integer n = (Integer)numbers.get(“two”);

        System.out.println(“two = ” + n);

        由于作為key的對象将通過計算其散列函數來确定與之對應的value的位置,是以任何作為key的對象都必須實作hashCode和equals方法。hashCode和equals方法繼承自根類Object,如果你用自定義的類當作key的話,要相當小心,按照散列函數的定義,           如果兩個對象相同,即obj1.equals(obj2)=true,則它們的hashCode必須相同,但如果兩個對象不同,則它們的hashCode不一定不同,如果兩個不同對象的hashCode相同,這種現象稱為沖突,沖突會導緻操作哈希表的時間開銷增大,是以盡量定義好的hashCode()方法,能加快哈希表的操作。

        如果相同的對象有不同的hashCode,對哈希表的操作會出現意想不到的結果(期待的get方法傳回null),要避免這種問題,隻需要牢記一條:要同時複寫equals方法和hashCode方法,而不要隻寫其中一個。

        Hashtable是同步的,線程安全。

HashMap類

        HashMap和Hashtable類似,不同之處在于HashMap是非同步的,并且允許null,即null value和null key。但是将HashMap視為Collection時(values()方法可傳回Collection),其疊代子操作時間開銷和HashMap的容量成比例。是以,如果疊代操作的性能相當重要的話,不要将HashMap的初始化容量設得過高,或者load factor過低。

TreeMap類

        基于紅黑樹資料結果的實作。檢視"鍵"或"鍵值對"時,它們會被排序(次序由Comparabel或Comparator決定)。TreeMap的特點在于,你得到的結果是經過排序的。TreeMap是唯一的帶有subMap()方法的Map,它可以傳回一個子樹。

WeakHashMap類

        WeakHashMap是一種改進的HashMap,它對key實行“弱引用”,如果一個key不再被外部所引用,那麼該key可以被GC回收。

Set接口

        Set是一種不包含重複的元素的Collection,即任意的兩個元素e1和e2都有e1.equals(e2) = false,是以Set最多有一個null元素。

        很明顯,Set的構造函數有一個限制條件,傳入的Collection參數不能包含重複的元素。

        請注意:必須小心操作可變對象(Mutable Object)。如果一個Set中的可變元素改變了自身狀态導緻Object.equals(Object)=true将導緻一些問題。

HashSet類

        HashSet是基于HashMap實作的,HashSet底層采用HashMap來儲存所有元素。為快速查找設計的Set。存入HashSet的對象必須定義hashCode()。hashCode和equal()是HashMap用的,因為無需排序是以隻需要關注定位和唯一性即可。hashCode用來計算hash值,hash值是用來确定hash表索引,hash表中的一個索引存放的是一張連結清單,是以還要通過equal方法循環比較鍊上的每一個對象才可以真正定位到鍵值對應的Entry。

        put時,如果hash表中沒定定位到,就在連結清單前加一個Entry,如果定位到了,則更換Entry中的value(值)并傳回舊value(值)。

        覆寫key的hashCode()和equal()時一定要注意,不要把它們和可變屬性關聯上,否則屬性變了之後hashCode會變,equal也會為false,這樣在Map中就找不到它了而且這樣的對象因為找不到它是以得不到釋放,這樣就變成了一個無效引用(相當于記憶體洩漏)。

LinkedHashSet類

        HashSet類的子類,具有HashSet的查詢速度,且内部使用連結清單維護元素的順序(插入的次序)。于是在使用疊代器周遊Set時,結果會按元素插入的次序顯示。

TreeSet類

        TreeSet是依靠TreeMap來實作的,TreeSet集合類是一個有序集合,它的元素按照升序排序,預設是自然順序排列,也就是說,TreeSet中的對象元素需要實作Comparable接口。TreeSet與HashSet類一樣沒有get()方法來擷取清單中的元素,是以也隻能通過疊代器方法來擷取。

        由于TreeMap需要排序,是以需要一個Comparable為鍵值進行大小比較,當然也是用Comparator定位的,Comparator可以在建立TreeMap時指定,這時排序時使用Comparator.compare,如果建立時沒有指定Comparator那麼就會使用key.compareTo()方法,這就要求key必須實作Comparable接口。

        TreeMap是使用Tree資料結構實作的,是以使用compare接口就可以完成定位了。

Queue接口

         Queue接口與List、Set同一級别,都是繼承了Collection接口。LinkedList實作了Queue接口。Queue接口窄化了對LinkedList的方法的通路權限(即在方法中的參數類型如果是Queue時,就完全隻能通路Queue接口所定義的方法了,而不能直接通路 LinkedList的非Queue的方法),以使得隻有恰當的方法才可以使用。

        隊列是一種資料結構。它有兩個基本操作:在隊列尾部加人一個元素,和從隊列頭部移除一個元素。就是說,隊列以一種先進先出的方式管理資料,如果你試圖向一個已經滿了的阻塞隊列中添加一個元素或者是從一個空的阻塞隊列中移除一個元索,将導緻線程阻塞。

        在多線程進行合作時,阻塞隊列是很有用的工具。工作者線程可以定期地把中間結果存到阻塞隊列中而其他工作者線線程把中間結果取出并在将來修改它們。隊列會自動平衡負載。如果第一個線程集運作得比第二個慢,則第二個線程集在等待結果時就會阻塞。如果第一個線程集運作得快,那麼它将等待第二個線程集趕上來。

隊列的方法

方法 說明 備注
add 增加一個元素 隊列已滿,抛出IIIegaISlabEepeplian
remove 移除并傳回隊列頭部元素 隊列為空,抛出oSuchElementException
element 傳回隊列頭部的元素 隊列為空,抛出NoSuchElementException
offer 添加一個元素并傳回true 如果隊列已滿,則傳回false
poll 移除并返問隊列頭部元素 如果隊列為空,則傳回null
peek 傳回隊列頭部的元素 如果隊列為空,則傳回null
put 添加一個元素 如果隊列滿,則阻塞
take 移除并傳回隊列頭部元素 如果隊列為空,則阻塞
remove、element、offer、poll、peek其實是屬于Queue接口。

        阻塞隊列的操作可以根據它們的響應方式分為以下三類:add、remove和element操作在你試圖為一個已滿的隊列增加元素或從空隊列取得元素時 抛出異常。當然,在多線程程式中,隊列在任何時間都可能變成滿的或空的,是以你可能想使用offer、poll、peek方法。這些方法在無法完成任務時 隻是給出一個出錯示而不會抛出異常。注意:poll和peek方法出錯進傳回null。是以,向隊列中插入null值是不合法的。

        offer,add差別:一些隊列有大小限制,是以如果想在一個滿的隊列中加入一個新項,多出的項就會被拒絕。這時新的offer方法就可以起作用了。它不是對調用add()方法抛出一個unchecked異常,而隻是得到由offer()傳回的false。

        poll,remove差別:remove()和poll()方法都是從隊列中删除第一個元素(head)。remove()的行為與Collection接口的版本相似,但是新的poll()方法在用空集合調用時不是抛出異常,隻是傳回null。是以新的方法更适合容易出現異常條件的情況。

        peek,element差別:element()和peek()用于在隊列的頭部查詢元素。與remove()方法類似,在隊列為空時,element()抛出一個異常,而peek()傳回null。

        注意:Queue隊列是不能直接執行個體化的。原先在java程式設計中,Queue的實作都是用LinkedList。如:Queue qe = new LinkedList();

        注意:此實作不是同步的。如果多個線程同時通路一個連結清單,而其中至少一個線程結構上修改了該清單,則它必須保證外部同步。(結構修改指添加或删除一個或多個元素的任何操作;僅設定元素的值不是結構修改)。這一般通過對自然封裝該清單的對象進行同步操作來完成。

ArrayBlockingQueue類

       一個由數組支援的有界隊列。基于數組的阻塞循環隊列,先進先出原則對元素進行排序。

LinkedBlockingQueue類

        一個由連結節點支援的可選有界隊列。基于連結清單的隊列,先進先出排序元素。

PriorityBlockingQueue類

        一個由優先級堆支援的無界優先級隊列。PriorityBlockingQueue是對PriorityQueue的再次包裝,是基于堆資料結構的,而PriorityQueue是沒有容量限制的,與ArrayList一樣,是以在優先阻塞隊列上put時是不會受阻的。但是由于資源被耗盡,是以試圖執行添加操作可能會導緻OutOfMemoryError),但是如果隊列為空,那麼取元素的操作take就會阻塞,是以它的檢索操作take是受阻的。另外,往入該隊列中的元素要具有比較能力。

DelayQueue

        一個由優先級堆支援的、基于時間的排程隊列。(基于PriorityQueue來實作的)是一個存放Delayed元素的無界阻塞隊列,隻有在延遲期滿時才能從中提取元素。該隊列的頭部是延遲期滿後儲存時間最長的Delayed元素。如果延遲都還沒有期滿,則隊列沒有頭部,并且poll将傳回null。當一個元素的getDelay(TimeUnit.NANOSECONDS)方法傳回一個小于或等于零的值時,則出現期滿,poll就以移除這個元素了。此隊列不允許使用null元素。

SynchronousQueue

一個利用BlockingQueue接口的簡單聚集(rendezvous)機制。SynchronousQueue 類是最簡單的。它沒有内部容量。它就像線程之間的手遞手機制。在隊列中加入一個元素的生産者會等待另一個線程的消費者。當這個消費者出現時,這個元素就直接在消費者和生産者之間傳遞,永遠不會加入到阻塞隊列中。

總結

        Java中的List/Set和Map的差別?

        List按對象進入的順序儲存對象,不做排序和編輯操作。

        Set對每個對象隻接受一次,并使用自己内部的排序方法(通常,你隻關心某個元素是否屬于Set而不關心它的順序--否則使用List)。

        Map同樣對每個元素儲存一份,但這是基于"鍵"(key)的,Map也有内置的排序,因而不關心元素添加的順序。如果添加元素的順序對程式設計很重要,應該使用LinkedHashSet或者LinkedHashMap。

        如何選用集合類?

        1、要求線程安全,使用Vector、Hashtable

        2、不要求線程安全,使用ArrayList, LinkedList, HashMap

        3、要求key和value鍵值,則使用HashMap, Hashtable

        4、資料量很大,又要線程安全,則使用Vector

        如果涉及到堆棧,隊列等操作,應該考慮用List,對于需要快速插入,删除元素,應該使用LinkedList,如果需要快速随機通路元素,應該使用ArrayList。

        如果程式在單線程環境中,或者通路僅僅在一個線程中進行,考慮非同步的類,其效率較高,如果多個線程可能同時操作一個類,應該使用同步的類。

        要特别注意對哈希表的操作,作為key的對象要正确複寫equals和hashCode方法。

       盡量傳回接口而非實際的類型,如傳回List而非ArrayList,這樣如果以後需要将ArrayList換成LinkedList時,用戶端代碼不用改變。這就是針對抽象程式設計。

互相差別

Vector和ArrayList

         1、vector是線程同步的,這個類中的一些方法保證了Vector中的對象是線程安全的。而arraylist是線程異步的,是不安全的。因為同步的要求會影響執行的效率,是以如果你不需要線程安全的集合那麼使用ArrayList是一個很好的選擇,這樣可以避免由于同步帶來的不必要的性能開銷。

         2、 如果集合中的元素的數目大于目前集合數組的長度時,vector增長率為目前數組長

度的100%,而arraylist增長率為目前數組長度的50%。是以如果你要在集合中儲存大量的資料那麼使用Vector有一些優勢,因為你可以通過設定集合的初始化大小來避免不必要的資源開銷。

        3、如果查找一個指定位置的資料,vector和arraylist使用的時間是相同的,都是0(1),這個時候使用vector和arraylist都可以。而如果移動一個指定位置的資料花費的時間為0(n-i),n為總長度,這個時候就應該考慮到使用linkedlist,因為它移動一個指定位置的資料所花費的時間為0(1),而查詢一個指定位置的資料時花費的時間為0(i)。

        ArrayList和Vector是采用數組方式存儲資料,此數組元素數大于實際存儲的資料以便增加和插入元素,都允許直接序号索引元素,但是插入資料要設計到數組元素移動等記憶體操作,是以索引資料快插入資料慢,Vector由于使用了synchronized方法(線程安全)是以性能上比ArrayList要差,LinkedList使用雙向連結清單實作存儲,按序号索引資料需要進行向前或向後周遊,但是插入資料時隻需要記錄本項的前後項即可,是以插入數度較快!

ArrayList和linkedlist

        1、ArrayList是實作了基于動态數組的資料結構,LinkedList基于連結清單的資料結構。

        2、對于随機通路get和set,ArrayList絕對優于LinkedList,因為LinkedList要移動指針。

        3、對于新增和删除操作add和remove,LinkedList比較占優勢,因為ArrayList要移動資料。這一點要看實際情況的。若隻對單條資料插入或删除,ArrayList的速度反而優于LinkedList。但若是批量随機的插入删除資料,LinkedList的速度大大優于ArrayList.因為ArrayList每插入一條資料,要移動插入點及之後的所有資料。

Hashtable和Hashmap

        1、曆史原因:Hashtable是基于陳舊的Dictionary類的,HashMap是Java1.2引進的Map接口的一個實作。

        2、同步性:Hashtable是線程安全的,也就是說是同步的,這個類中的一些方法保證了Hashtable中的對象是線程安全的。而HashMap則是線程異步的,是以HashMap中的對象并不是線程安全的。因為同步的要求會影響執行的效率,是以如果你不需要線程安全的集合那麼使用HashMap是一個很好的選擇,這樣可以避免由于同步帶來的不必要的性能開銷,進而提高效率。

        3、HashMap可以讓你将空值作為一個表的條目的key或value,但是Hashtable是不能放入空值的(null)。

HashMap和TreeMap

        1、HashMap通過hashcode對其内容進行快速查找,而TreeMap中所有的元素都保持着某種固定的順序,如果你需要得到一個有序的結果你就應該使用TreeMap(HashMap中元素的排列順序是不固定的)。

        2、在Map中插入、删除和定位元素,HashMap是最好的選擇。但如果你要按自然順序或自定義順序周遊鍵,那麼TreeMap會更好。使用HashMap要求添加的鍵類明确定義了hashCode()和equals()的實作。這個TreeMap沒有調優選項,因為該樹總處于平衡狀态。

HashSet與TreeSet

        HashSet是基于hash算法實作的,性能優于TreeSet。通常使用HashSet,在我們需要對其中元素排序的時候才使用TreeSet。

Iterator(疊代器)的用法

        不論Collection的實際類型如何,它都支援一個iterator()的方法,該方法傳回一個疊代子,使用該疊代子即可逐一通路Collection中每一個元素。

        Iterator模式是用于周遊集合類的标準通路方法。它可以把通路邏輯從不同類型的集合類中抽象出來,進而避免向用戶端暴露集合的内部結構。

        例如,如果沒有使用Iterator,周遊一個數組的方法是使用索引:for(inti=0;i<array.size();i++) {..get(i)...}

        而通路一個連結清單(LinkedList)又必須使用while循環:while((e=e.next())!=null){...e.data()...}

        以上兩種方法用戶端都必須事先知道集合的内部結構,通路代碼和集合本身是緊耦合,無法将通路邏輯從集合類和用戶端代碼中分離出來,每一種集合對應一種周遊方法,用戶端代碼無法複用。

        更恐怖的是,如果以後需要把ArrayList更換為LinkedList,則原來的用戶端代碼必須全部重寫。

        為解決以上問題,Iterator模式總是用同一種邏輯來周遊集合:

         for(Iteratorit=c.iterater(); it.hasNext();) { ...}

        奧秘在于用戶端自身不維護周遊集合的"指針",所有的内部狀态(如目前元素位置,是否有下一個元素)都由Iterator來維護,而這個Iterator由集合類通過工廠方法生成,是以,它知道如何周遊整個集合。

        用戶端從不直接和集合類打交道,它總是控制Iterator,向它發送"向前","向後","取目前元素"的指令,就可以間接周遊整個集合。

首先看看java.util.Iterator接口的定義:

public interface Iterator {
 boolean hasNext();
 Object next();
 void remove();
}
           

依賴前兩個方法就能完成周遊,典型的代碼如下:

for(Iterator it=c.iterator();it.hasNext();){
Object o=it.next();
  // 對o的操作...
}
           

在JDK1.5中,還對上面的代碼在文法上作了簡化:

// Type是具體的類型,如String。
for(Type t:c){
// 對t的操作...
}
           

        每一種集合類傳回的Iterator具體類型可能不同,Array可能傳回ArrayIterator,Set可能傳回SetIterator,Tree可能傳回TreeIterator,但是它們都實作了Iterator接口,是以,用戶端不關心到底是哪種Iterator,它隻需要獲得這個Iterator接口即可,這就是面向對象的威力。

        要確定周遊過程順利完成,必須保證周遊過程中不更改集合的内容(Iterator的remove()方法除外),是以,確定周遊可靠的原則是隻在一個線程中使用這個集合,或者在多線程中對周遊代碼進行同步。

Iterator示例:

Collection c=new ArrayList();
c.add("abc");
c.add("xyz");
for(Iterator it=c.iterator();it.hasNext();){
         Strings=(String)it.next();
         System.out.println(s);
}
           

        如果你把第一行代碼的 ArrayList 換成 LinkedList 或 Vector ,剩下的代碼不用改動一行就能編譯,而且功能不變,這就是針對抽象程式設計的原則:對具體類的依賴性最小。