1. 程式人生 > >Java集合類解析

Java集合類解析

先來 重新 興趣 exp weak hashtable 過程 子類 put

Java中的集合類包含的內容很多而且很重要,很多數據的存儲和處理(排序,去重,篩選等)都需要通過集合類來完成。


首先java中集合類主要有兩大分支:

(1)Collection (2)Map

先看它們的類圖:

(1)Collection

技術分享

(2)Map
技術分享

可以看到它們之間的關系紛繁復雜,如果不系統的學習一下,還真是不知道有什麽區別,該怎麽選擇。由於HashSet的內部實現原理是使用了HashMap,所以我們的學習路線為先學習Map集合類,然後再來學習Collection集合類。


(1)HashMap和Hashtable ( 註意table是小寫的t,搞不懂為什麽要這樣,老是會寫錯。。。)

首先來看HashMap和HashTable,這兩兄弟經常被放到一起來比較,那麽它們有什麽不一樣呢?

a.HashMap不是線程安全的;HashTable是線程安全的,其線程安全是通過Sychronize實現。

b.由於上述原因,HashMap效率高於HashTable。

c.HashMap的鍵可以為null,HashTable不可以。

d.多線程環境下,通常也不是用HashTable,因為效率低。HashMap配合Collections工具類使用實現線程安全。同時還有ConcurrentHashMap可以選擇,該類的線程安全是通過Lock的方式實現的,所以效率高於Hashtable。

好,比較了他們的不一樣後,來講講它們的原理。

數組,鏈表,哈希表。各有優劣,順便提一下,數組連續內存空間,查找速度快,增刪慢;鏈表充分利用了內存,存儲空間是不連續的,首尾存儲上下一個節點的信息,所以尋址麻煩,查找速度慢,但是增刪快;哈希表呢,綜合了它們兩個的有點,一個哈希表,由數組和鏈表組成。假設一條鏈表有1000個節點,現在查找最後一個節點,就得從第一個遍歷到最後一個;如果用哈希表,將這條鏈表分為10組,用一個容量為10數組來存儲這10組鏈表的頭結點(a[0] = 0 , a[1] = 100 , a[2] = 200 …)。這樣尋址就快了。

HashMap實現原理就是上述原理了,當然其具體實現還有很多其他的東西。Hashtable同理,只不過做了同步處理。

Hash碰撞,不同的key根據hash算法算出的值可能一樣,如果一樣就是所謂的碰撞。

優化措施:

(1) HashMap的擴容代價非常大,要生成一個新的桶數組,然後要把所有元素都重新Hash落桶一次,幾乎等於重新執行了一次所有元素的put。所以如果我們對Map的大小有一個範圍的話,可以在構造時給定大小,一般大小設置為:(int) ((float) expectedSize / 0.75F + 1.0F)。

(2) key的設計盡量簡潔。


HashMap一些功能實現:

a.按值排序

HashMap按值排序通過Collections的sort方法,在實現排序之前,我們先看看HashMap的幾種遍歷方式:

    //Collection And Map
    public static void testCM(){
        //Collection
        Map<Integer , String> hs = new HashMap<Integer , String>();
        int i = 0;
        hs.put(199, "序號:"+201);
        while(i<50){
            hs.put(i, "序號:"+i);
            i++;
        }
        hs.put(-1, "序號:"+200);
        hs.put(200, "序號:"+200);

        //遍歷方式一:for each遍歷HashMap的entryset,註意這種方式在定義的時候就必須寫成
        //Map<Integer , String> hs,不能寫成Map hs;
        for(Entry<Integer , String> entry : hs.entrySet()){
            System.out.println("key:"+entry.getKey()+"  value:"+entry.getValue());
        }
        //遍歷方式二:使用EntrySet的Iterator
        Iterator<Map.Entry<Integer , String>> iterator = hs.entrySet().iterator();
        while(iterator.hasNext()){
            Entry<Integer , String> entry =  iterator.next();
            System.out.println("key:"+entry.getKey()+"  value:"+entry.getValue());
        };
        //遍歷方式三:for each直接使用HashMap的keyset
        for(Integer key : hs.keySet()){
            System.out.println("key:"+key+"  value:"+hs.get(key));
        };
        //遍歷方式四:使用keyset的Iterator
        Iterator keyIterator = hs.keySet().iterator();
        while(keyIterator.hasNext()){
            Integer key = (Integer)keyIterator.next();
            System.out.println("key:"+key+"  value:"+hs.get(key));
        }   
    }
(1)使用keyset的兩種方式都會遍歷兩次,所以效率沒有使用EntrySet高。
(2)HashMap輸出是無序的,這個無序不是說每次遍歷的結果順序不一樣,而是說與插入順序不一樣。

接下來我們看按值排序,註釋比較詳細就不贅述過程了。

    //對HashMap排序
    public static void sortHashMap(Map<Integer , String> hashmap){

        System.out.println("排序後");

        //第一步,用HashMap構造一個LinkedList
        Set<Entry<Integer , String>> sets = hashmap.entrySet();
        LinkedList<Entry<Integer , String>> linkedList = new LinkedList<Entry<Integer , String>>(sets);

        //用Collections的sort方法排序
        Collections.sort(linkedList , new Comparator<Entry<Integer , String>>(){

            @Override
            public int compare(Entry<Integer , String> o1, Entry<Integer , String> o2) {
                // TODO Auto-generated method stub
                /*String object1 = (String) o1.getValue();
                String object2 = (String) o2.getValue();
                return object1.compareTo(object2);*/
                return o1.getValue().compareTo(o2.getValue());
            }

        });

        //第三步,將排序後的list賦值給LinkedHashMap
        Map<Integer , String> map = new LinkedHashMap();
        for(Entry<Integer , String> entry : linkedList){
            map.put(entry.getKey(), entry.getValue());
        }
        for(Entry<Integer , String> entry : map.entrySet()){
            System.out.println("key:"+entry.getKey()+"  value:"+entry.getValue());
        }
    }

b.按鍵排序

HashMap按鍵排序要比按值排序方法容易實現,而且方法很多,下面一一介紹。

第一種:還是熟悉的配方還是熟悉的味道,用Collections的sort方法,只是更改一下比較規則。

第二種:TreeMap是按鍵排序的,默認升序,所以可以通過TreeMap來實現。

    public static void sortHashMapByKey(Map hashmap){

        System.out.println("按鍵排序後");

        //第一步:先創建一個TreeMap實例,構造函數傳入一個Comparator對象。
        TreeMap<Integer , String> treemap = new TreeMap<Integer , String>(new Comparator<Integer>(){

            @Override
            public int compare(Integer o1,Integer o2) {
                // TODO Auto-generated method stub
                return Integer.compare(o1, o2);
            }

        });
        //第二步:將要排序的HashMap添加到我們構造的TreeMap中。
        treemap.putAll(hashmap);
        for(Entry<Integer , String> entry : treemap.entrySet()){
            System.out.println("key:"+entry.getKey()+"  value:"+entry.getValue());
        }
    }

第三種:可以通過keyset取出所有的key,然後將key排序,再有序的將key-value鍵值對存到LinkedHashMap中,這個就不貼代碼了,有興趣的可以自己去嘗試一下。

c.value去重

對於HashMap而言,它的key是不能重復的,但是它的value是可以重復的,有的時候我們要將重復的部分剔除掉。

方法一:將HashMap的key-value對調,然後賦值給一個新的HashMap,由於key的不可重復性,此時就將重復值去掉了。最後將新得到的HashMap的key-value再對調一次即可。

d.HashMap線程同步

第一種:

Map<Integer , String> hs = new HashMap<Integer , String>();
        hs = Collections.synchronizedMap(hs);

第二種:

ConcurrentHashMap<Integer , String> hs = new ConcurrentHashMap<Integer , String>();

(2)IdentifyHashMap

IdentityHashMap與HashMap基本相似,只是當兩個key嚴格相等時,即key1==key2時,它才認為兩個key是相等的 。IdentityHashMap也允許使用null,但不保證鍵值對之間的順序。


(3)WeakHashMap

WeakHashMap與HashMap的用法基本相同,區別在於:後者的key保留對象的強引用,即只要HashMap對象不被銷毀,其對象所有key所引用的對象不會被垃圾回收,HashMap也不會自動刪除這些key所對應的鍵值對對象。但WeakHashMap的key所引用的對象沒有被其他強引用變量所引用,則這些key所引用的對象可能被回收。WeakHashMap中的每個key對象保存了實際對象的弱引用,當回收了該key所對應的實際對象後,WeakHashMap會自動刪除該key所對應的鍵值對。


接下來是Collection接口及其子類:

(4)ArrayList , LinkedList , Vector

(1)首先,說說它們的關系和區別。ArrayList和Vector本質都是用數組實現的,而LinkList是用雙鏈表實現的;所以,Arraylist和Vector在查找效率上比較高,增刪效率比較低;LinkedList則正好相反。ArrayList是線程不安全的,Vector是線程安全的,效率肯定沒有ArrayList高了。實際中一般也不怎麽用Vector,可以自己做線程同步,也可以用Collections配合ArrayList實現線程同步。

(2)Tips

前面多次提到擴容的代價很高,所以如果能確定容量的大致範圍就可以在創建實例的時候指定,註意,這個僅限於ArrayList和Vector喲:

ArrayList arrayList = new ArrayList(100);
arrayList.ensureCapacity(200);
Vector vector = new Vector(100);
vector.ensureCapacity(200);

(3)其他功能實現

a.排序

List的排序的話就是使用Collections的sort方法,構造Comparator或者讓List中的對象實現Comparaable都可以,這裏就不貼代碼了。

b.去重

第一種:用Iterator遍歷,遍歷出來的放到一個臨時List中,放之前用contains判斷一下。

第二種:利用set的不可重復性,只需三步走。

//第一步:用HashSet的特性去重
    HashSet tempSet = new HashSet(arrayList);
    //第二步:將arrayList清除
    tempSet.clear();
    //第三步:將去重後的重新賦給List
    arrayList.addAll(tempSet);

(5)Stack

Stack呢,是繼承自Vector的,所以用法啊,線程安全什麽的跟Vector都差不多,只是有幾個地方需要註意:

第一:add()和push(),stack是將最後一個element作為棧頂的,所以這兩個方法對stack而言是沒什麽區別的,但是,它們的返回值不一樣,add()返回boolean,就是添加成功了沒有;push()返回的是你添加的元素。為了可讀性以及將它跟棧有一丟丟聯系,推薦使用push。

第二:peek()和pop(),這兩個方法都能得到棧頂元素,區別是peek()只是讀取,對原棧沒有什麽影響;pop(),從字面上就能理解,出棧,所以原棧的棧頂元素就沒了。


(6)HashSet和TreeSet

Set集合類的特點就是可以去重,它們的內部實現都是基於Map的,用的是Map的key,所以知道為什麽可以去重復了吧。
既然要去重,那麽久需要比較,既然要比較,那麽久需要了解怎麽比較的,不然它將1等於2了,你怎麽辦?

比較是基於hascode()方法和equals()方法的,所以必要情況下需要重新這兩個方法。


好了,到了總結的時候了,其實你會發現集合類雖然看起來多,但是都是很有規律的。ArrayList,LinkedList一個無序,一個有序;HashSet,TreeSet一個無序,一個有序;HashMap,LinkedHasmMap,一個無序,一個有序;Vector和HashTable,Stack是線程安全的,但是效率低;線程不安全的類都可以配合Collections得到線程安全的類。
版權聲明:本文為博主原創文章,未經博主允許不得轉載。

Java集合類解析