Java基礎之集合篇(模塊記憶、精要分析)

 

千里之行,始於足下。把別人的變成本身,再把本身的分享給別人,這也是一次提高的過程。本文的目的是以一篇文章從總體把握集合體系又不失一些細節上的實現,高手路過。java

集合的做用與特色

Java是一門面向對象語言,數據多了用對象封裝存儲(好比,人有姓名、年齡、性別等數據信息,咱們就抽象一個Person對象來封裝存儲),對象多了又用什麼來存儲呢?集合,集合就是用來存儲對象的。數組

 

集合的特色就是適用於存儲對象並且能夠存儲不一樣類型的對象,集合的長度是可變的。安全

 

集合框架圖

集合既然能存儲不一樣類型的的對象,那麼集合體系中確定有不一樣類型的容器,集合中主要有List、SetMap三種容器,每種容器的數據結構都不同。集合體系的類、接口很是多,方便記憶,咱們將下面這個相對簡潔的集合框架圖分爲從5個模塊來學習:數據結構

1.Collection集合框架

2.Map集合dom

3.集合遍歷ide

4.集合比較函數

5.集合工具類工具

 

wps13E.tmp 

先對圖作一下解析:源碼分析

l 點線框表示接口

l 實現框表示具體的類

l 帶有空心箭頭的點線表示一個具體的類實現了一個接口

l 實心箭頭表示某一個類能夠生成箭頭所指向的類

l 經常使用的容器用黑色粗線表示

 

Collection集合

Collection接口

Collection是一個接口,是高度抽象出來的集合,包含了集合的基本操做:添加、刪除、清空、遍歷、是否爲空、獲取大小等等。定義以下:

public interface Collection<E> extends Iterable<E> {}

Collection接口下主要有兩個子接口:ListSet

List接口

List是Collection的一個子接口,List中的元素是有序的,能夠重複,有索引

定義以下:

public interface List<E> extends Collection<E> {}

wps13F.tmp 

 

List是繼承於Collection接口,它天然就包含了Collection中的所有函數接口,因爲List有本身的特色(素是有序的,能夠重複,有索引),所以List也有本身特有的一些操做,如上紅色框框中的。List特有的操做主要是帶有索引的操做和List自身的迭代器(如上圖)。List接口的經常使用實現類有ArrayListLinkedList

 

ArrayList

ArrayList是List接口的一個具體的實現類。ArrayList的底層數據結構是數組結構,至關於一個動態數組。ArrayList的特色是隨機查詢速度快、增刪慢、線程不安全。ArrayListVector的區別是:Vector是線程安全的(synchronized實現)。定義以下:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{}

l ArrayList 繼承AbstractListAbstractList是對一些公有操做的再次抽象,抽出一些特性操做,如根據索引操做元素,生產迭代器等。

l ArrayList 實現了RandmoAccess接口,即提供了隨機訪問功能,能夠經過元素的索引快速獲取元素對象

l ArrayList 實現了Cloneable接口,即覆蓋了函數clone(),能被克隆。

l ArrayList 實現java.io.Serializable接口,這意味着ArrayList支持序列化,能經過序列化去傳輸。

 

ArrayList源碼分析總結(源碼略,建議先總體認識再打開源碼對着看):

 

l 能夠經過構造函數指定ArrayList的容量大小,若不指定則默認是10

l 添加新元素時,容量不足以容納所有元素時,會從新建立一個新的足以容納所有元素的數組,把舊數組中的元素拷貝到新的數組中並返回新的數組。增量具體看源碼。

l 添加的新元素添加到數組末尾。

l 查找、刪除元素使用equals方法比較對象

l ArrayList的克隆函數,便是將所有元素克隆到一個數組中。

l ArrayList的序列化,即經過java.io.ObjectOutputStreamArrayList的長度和每個元素寫到輸出流中,或者經過java.io.ObjectInputStream從輸入流中讀出長度和每個元素到一個數組中。

LinkedList

LinkedList是List接口的一個具體的實現類。LinkedList底層數據結構是雙向的鏈表結構,能夠當成堆棧或者隊列來使用。LinkedList的特色是增刪速度很快,順序查詢效率較高,隨機查詢稍慢。定義以下:

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{} 

l LinkedList 是繼承AbstractSequentialListAbstractSequentialListAbstractList的一個子類。

l LinkedList 實現 Deque 接口,即能將LinkedList看成雙端隊列使用。

l 同ArrayList,LinkedList一樣支持克隆和序列化。

 

LinkedList 源碼分析總結(源碼略,建議先總體認識再打開源碼對着看):

 

l LinkedList的本質是一個雙向鏈表,各有一個頭節點和尾節點,每個節點都有個數據域、一個指向上一個節點的指針域、一個指向上下一個節點的指針域。

l 添加元素時,將元素添加到雙向鏈表的末端。

l 查找、刪除元素時,使用equals方法比較對象

l 根據索引查找元素時,若索引值index < 雙向鏈表長度的1/2,則從表頭日後查找,不然從表尾往前找

l LinkedList的克隆和序列化原理同ArrayList

l LinkedList實現了DequeDeque接口定義了在雙端隊列兩端訪問元素的方法,每種方法都存在兩種形式:一種是在操做失敗時拋出異常,另外一種是返回一個特殊值(null 或 false)。

l LinkedList有本身特有的迭代器ListIterator

l LinkedList不存在擴容增量的問題。

List應用舉例

LinkedList實現堆棧和隊列

LinkedList能夠當成堆棧和隊列使用,由於實現了Deque接口,Deque接口提供了一些了出棧入棧和出隊入隊的方法。

class MyQueueStack {

    private LinkedList link;
    
    MyQueueStack() {
        link = new LinkedList();
    }
    
    public void add(Object obj) {
        link.addFirst(obj);
    }
    
    public Object remove() {
        //return link.removeLast();//隊列
        return link.removeFirst();//
    }
    
    public boolean isNull() {
        if(!link.isEmpty()) {
            return false;
        }
        return true;
    }

}

public class LinkedListQueueStack {
    public static void main(String[] args) {
        MyQueueStack q = new MyQueueStack();
        q.add("001");
        q.add("002");
        q.add("003");
        
        while(!q.isNull())
            System.out.println(q.remove());
    }
}

隊列運行結果:
001
002
003
棧運行結果:
003
002
001
View Code

ArrayList保證惟一性

List中的元素是能夠重複的,能夠經過重寫equals方法實現惟一性。下面是ArrayList實現元素去重(依據equals)的一個例子。

 

class Teacher {
    private String name;
    private String age;
    
    Teacher(String name,String age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAge() {
        return age;
    }
    public void setAge(String age) {
        this.age = age;
    }
    @Override
    public boolean equals(Object obj) {
        if(!(obj instanceof Teacher)) {
            return false;
        }
        Teacher p = (Teacher)obj;
        System.out.println("調用equals比較" + p.name + "與" + this.name);
        return p.name.equals(this.name) && p.age.equals(this.age);
    }
}

public class ArrayListDuplicate {
    
    public static void main(String[] args) {
        ArrayList al = new ArrayList();
        al.add(new Teacher("zhangsan","20"));
        al.add(new Teacher("lingsi","21"));
        al.add(new Teacher("wangwu","22"));
        al.add(new Teacher("wangwu","22"));
        
        print("-----------原始ArrayList----------");
        
        Iterator it = al.iterator();
        while(it.hasNext()) {
            Teacher obj = (Teacher)it.next();
            print(obj.getName() + "-" + obj.getAge());
        }
        
        print("-----------刪除重複元素----------");
        ArrayList al2 = removeSameObj(al);
        
        Iterator it2 = al2.iterator();
        while(it2.hasNext()) {
            Teacher obj = (Teacher)it2.next();
            print(obj.getName() + "-" + obj.getAge());
        }
        
        print("-----------刪除lingsi元素----------");
        al2.remove(new Teacher("lingsi","21"));
        
        Iterator it3 = al2.iterator();
        while(it3.hasNext()) {
            Teacher obj = (Teacher)it3.next();
            print(obj.getName() + "-" + obj.getAge());
        }
        
    }
    
    public static ArrayList removeSameObj(ArrayList al) {
        ArrayList newAl = new ArrayList();
        Iterator it = al.iterator();
        while(it.hasNext()) {
            Object obj = it.next();
            if(!newAl.contains(obj)) {
                newAl.add(obj);
            }
        }
        return newAl;
    }
    
    public static void print(Object obj) {
        System.out.println(obj);
    }
    
}

運行結果:
-----------原始ArrayList----------
zhangsan-20
lingsi-21
wangwu-22
wangwu-22
-----------刪除重複元素----------
調用equals比較zhangsan與lingsi
調用equals比較zhangsan與wangwu
調用equals比較lingsi與wangwu
調用equals比較zhangsan與wangwu
調用equals比較lingsi與wangwu
調用equals比較wangwu與wangwu
zhangsan-20
lingsi-21
wangwu-22
-----------刪除lingsi元素----------
調用equals比較zhangsan與lingsi
調用equals比較lingsi與lingsi
zhangsan-20
wangwu-22
View Code

從運行結果中能夠看出,調用list的contains方法和remove方法時,調用了equals方法進行比較(List集合中判斷元素是否相等依據的是equals方法)

 

Set接口

Set是Collection的一個子接口,Set中的元素是無序的,不能夠重複。定義以下:

public interface Set<E> extends Collection<E> {}

Set是繼承於Collection接口,它天然就包含了Collection中的所有函數接口。Set接口的經常使用實現類有HashSetTreeSet實際上HashSet是經過HashMap實現的,TreeSet是經過TreeMap實現的。在此只簡要列舉它們的一些特色,能夠直接看下面的HashMap和TreeMap的分析,理解了HashMapTreeMap,也就理解了HashSetTreeSet

HashSet

HashSet是Set接口的一個具體的實現類。其特色是元素是無序的(取出順序和存入順序不必定同樣),不能夠重複。定義以下:

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{}

l HashSet繼承AbstractSetAbstractSet抽象了equals()hashCode()removeAll()三個方法。

l HashSet實現了Cloneable,Serializable接口以便支持克隆和序列化

l HashSet保證元素惟一性依賴equals()方法和hashCode()方法。

 

TreeSet

TreeSet是Set接口的一個具體的實現類。其特色是元素是有序的,不能夠重複。定義以下:

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{}

l TreeSet繼承AbstractSetAbstractSet抽象了equals()hashCode()removeAll()三個方法。

l TreeMap實現了NavigableMap接口,支持一系列的導航方法,好比返回有序的key集合。

l TreeSet實現了Cloneable,Serializable接口以便支持克隆和序列化

l TreeSet保證元素惟一性依賴Comparator接口和Comparable接口。

Map集合

Map接口

不一樣於List和Set,集合體系中的Map是「雙列集合」,存儲的是內容是鍵值對(key-value)Map集合的特色是不能包含重複的鍵,每個鍵最多能映射一個值。Map接口抽象出了基本的增刪改查操做。定義以下:

public interface Map<K,V> {}

主要方法

/**添加**/
V put(K key, V value);
void putAll(Map<? extends K, ? extends V> m);
 
/**刪除**/
void clear();
V remove(Object key);
 
/**判斷**/
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
 
/**獲取**/
int size();
V get(Object key);
Collection<V> values();
Set<K> keySet();
Set<Map.Entry<K, V>> entrySet();

Map接口的主要實現類有HashMapTreeMap

HashMap

HashMap是Map接口的一個實現類,它存儲的內容是鍵值對(key-value)HashMap的底層是哈希表數據結構,容許存儲null的鍵和值,同時HashMap是線程不安全的。HashMap與HashTable的區別是:HashTable不能夠存儲null的鍵和值,HashTable是線程安全的(synchronized實現)

 

哈希表的定義:給定表M,存在函數f(key),對任意給定的關鍵字值key,代入函數後若能獲得包含該關鍵字的記錄在表中的地址,則稱表M爲哈希(Hash)表,函數f(key)爲哈希(Hash) 函數。經過把關鍵字值key映射到哈希表中的一個位置來訪問記錄,以加快查找的速度。

 

HashMap定義以下:

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
{}

l HashMap繼承AbstractMap,AbstractMap抽象了Map集合的一些基本操做。

l HashMap實現了Cloneable,Serializable接口以便支持克隆和序列化。

 

HashMap源碼分析總結(源碼略,建議先總體認識再打開源碼對着看):

 

l HashMap的幾個重要成員:table, DEFAULT_INITIAL_CAPACITY ,MAXIMUM_CAPACITY ,

size, threshold, loadFactor, modCount。

(1)table是一個Entry[]數組類型,Map.Entry是一個Map接口裏的一個內部接口。而Entry實際上就是一個單向鏈表,也稱爲HashMap的「數組+鏈式存儲法」,鏈式存儲是爲了解決哈希衝突而存在HashMap的鍵值對就是存儲在Entry對象中(一個Entry對象包含一個key、一個value、key對應的hash和指向下一個Entry的對象)。

(2)DEFAULT_INITIAL_CAPACITY是默認的初始容量是16。

(3)MAXIMUM_CAPACITY, 最大容量(初始化容量大於這個值時將被這個值替換)。

(4)size是HashMap的大小,保存的鍵值對的數量。

(5)threshold是HashMap的閾值,用於判斷是否須要調整HashMap的容量threshold的值="容量*加載因子",當HashMap中存儲數據的數量達到threshold時,就須要將HashMap進行rehash 操做(即重建內部數據結構)是容量*2。

(6)loadFactor就是加載因子。

(7)modCount是記錄HashMap被修改的次數。

 

l table分配空間。不一樣版本略有差別。在1.6的源碼中,爲table分配空間是在HashMap的構造函數中,而1.7的中,則在put的時候先判斷table是否爲空,爲空則分配空間。

l get(Object key)。先計算key的hash,根據hash計算索引,根據索引在table數組中查找出對應的Entry,返回Entry中的value。

l put(K key, V value)。若key爲null,則建立一個key爲null的Entry對象並存儲到table[0]中。不然計算其hash以及索引i,建立一個鍵值對和哈希值分別爲key、value、hash的Entry對象並存儲到table[i]中(若table[i]處爲空即沒有Entry鏈表則直接存入,不然將Entry對象添加到table[i]處的Entry鏈表的表頭中)。須要注意的是,若是該hash已經存在Entry鏈表中,則用新的value取代舊的value並返回舊的value。

l  putAll(Map<? extends K, ? extends V> m)。噹噹前實際容量小於須要的容量,則將容量*2。調用put()方法逐個添加到HashMap中。

l remove(Object key)。計算key的hash以及索引i,根據索引在table數組中查找出對應的Entry,從Entry鏈表刪除對應的Entry節點,期間會比較hash和key(equals判斷)來判斷是不是要刪除的節點。

l clear()。把每個table[i]設置爲null。

l Entry類中重寫了equals()方法和hashCode()來判斷兩個Entry是否相等

l containsKey(Object key)。比較hash和key(equals判斷)來判斷是不是包含該key。

 

 

 

HashMap內存結構示意圖

 

HashMap舉例

經過keyset遍歷

public class HashMapTest {

    public static void main(String[] args) {
        HashMap<String,String> map = new HashMap<String,String>();
        map.put("001", "zhangsan1");
        map.put("002", "zhangsan2");
        map.put("003", "zhangsan3");
        map.put("004", "zhangsan4");
        
        Set<String> keySet = map.keySet();
        Iterator<String> it = keySet.iterator();
        
        while(it.hasNext()) {
            String obj = (String)it.next();
            System.out.println(obj + "-" + map.get(obj));
        }
    }
}

運行結果:
004-zhangsan4
001-zhangsan1
002-zhangsan2
003-zhangsan3
View Code

經過entrySet遍歷

public class HashMapTest {

    public static void main(String[] args) {
        HashMap<String,String> map = new HashMap<String,String>();
        map.put("001", "zhangsan1");
        map.put("002", "zhangsan2");
        map.put("003", "zhangsan3");
        map.put("004", "zhangsan4");

        Set<Entry<String, String>> se = map.entrySet();
        Iterator<Entry<String, String>> it = se.iterator();
        while(it.hasNext()) {
            Entry<String, String> en = (Entry<String, String>)it.next();
            System.out.println(en.getKey() + "-" + en.getValue());
        }
        
    }
}

運行結果:
004-zhangsan4
001-zhangsan1
002-zhangsan2
003-zhangsan3
View Code

TreeMap

TreeMap是Map接口的一個實現類,所以它存儲的也是鍵值對(key-value)TreeMap的底層數據結構是二叉樹(紅黑樹),其特色是能夠對元素進行排序,TreeMap是線程不安全的。定義以下:

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{}

l TreeMap繼承AbstractMap,AbstractMap抽象了Map集合的一些基本操做。

l TreeMap實現了NavigableMap接口,支持一系列的導航方法,好比返回有序的key集合。

l TreeMap實現了Cloneable,Serializable接口以便支持克隆和序列化。

 

TreeMap源碼分析總結(源碼略,建議先總體認識再打開源碼對着看):

 

l TreeMap的幾個重要成員:root, size, comparator。

(1)root是紅黑樹的根節點,它是Entry類型,Entry是紅黑數的節點,它包含了紅黑數的6個基本組成成分:key(鍵)、value(值)、left(左孩子)、right(右孩子)、parent(父節點)、color(顏色)。Entry節點根據key進行排序,Entry節點包含的內容爲value。

(2)comparator是比較器,使紅黑樹的節點具備比較性,用來給TreeMap排序。紅黑數排序時,根據Entry中的key進行排序,Entry中的key比較大小是根據比較器comparator來進行判斷的。

(3)size是紅黑樹節點的個數。

 

l TreeMap(Comparator<? super K> comparator)。帶比較器的構造函數能夠在TreeMap外部自定義TreeMap元素(紅黑樹節點)的比較器。

l get(Object key)。若比較器comparator不爲空(即經過構造函數外部傳入的),則經過Comparator接口的compare()方法從根節點開始和key逐個比較直到找出相等的節點。若比較器爲空,則使用Comparable接口的compareTo()比較查找

l put(K key, V value)。若根節點root爲空,則根據key-value建立一個Entry對象插入到根節點。不然在紅黑樹中找到找到(key, value)的插入位置。查找插入位置仍是若比較器comparator不爲空則經過Comparator接口的compare()方法比較查找,不然經過Comparable接口的compareTo()方法比較查找。過程比較複雜。

l putAll(Map<? extends K, ? extends V> map)。調用AbstractMap中的putAll(),AbstractMap中的putAll()又會調用到TreeMap的put()。

l remove(Object key)。先根據key查找出對應的Entry,從紅黑樹中刪除對應的Entry節點。

l clear()。clear把根節點設置爲null便可。

l containsKey(Object key)。判斷是不是包含該key的元素,查找方法同get()。

 

TreeMap舉例

public class TreeMapTest {

    public static void main(String[] args) {
        System.out.println("---TreeMap默認按key升序排序---");
        TreeMap<Integer,String> map = new TreeMap<Integer,String>();
        map.put(200,"sha");
        map.put(300,"can");
        map.put(100,"pek");
        map.put(400,"szx");
        
        Set<Entry<Integer,String>> se = map.entrySet();
        Iterator<Entry<Integer,String>> it = se.iterator();
        while(it.hasNext()) {
            Entry<Integer,String> en = (Entry<Integer,String>)it.next();
            System.out.println(en.getKey() + "-" + en.getValue());
        }
        
        System.out.println("---TreeMap使用自定義比較器降序排序---");
        TreeMap<Integer,String> map2 = new TreeMap<Integer,String>(new                         CityNumComparator());
        map2.put(200,"sha");
        map2.put(300,"can");
        map2.put(100,"pek");
        map2.put(400,"szx");
        
        Set<Entry<Integer,String>> se2 = map2.entrySet();
        Iterator<Entry<Integer,String>> it2 = se2.iterator();
        while(it2.hasNext()) {
            Entry<Integer,String> en = (Entry<Integer,String>)it2.next();
            System.out.println(en.getKey() + "-" + en.getValue());
        }
        
    }
}

class CityNumComparator implements Comparator {
    @Override
    public int compare(Object o1, Object o2) {
        if(!(o1 instanceof Integer) || !(o2 instanceof Integer)) {
            throw new RuntimeException("不可比較的對象");
        }
        Integer num1 = (Integer)o1;
        Integer num2 = (Integer)o2;
        
        if(num1 < num2) {
            return 1;
        }
        if(num1 > num2) {
            return -1;
        }
        return 0;
        //Integer類型自身已經自有比較性,或者
        //return num2.compareTo(num1);
    }
} 

運行結果:
---TreeMap默認按key升序排序---
100-pek
200-sha
300-can
400-szx
---TreeMap使用自定義比較器降序排序---
400-szx
300-can
200-sha
100-pek
View Code

因而可知,要想按照本身的方式對TreeMap進行排序,能夠自定義比較器重寫compare()方法重寫本身的比較邏輯

集合遍歷

迭代器的設計原理

在上面List和Map的分析和舉例中,已經提到迭代器了。由於每一種容器的底層數據結構不一樣,因此迭代的方式也不一樣,可是也有共同的部分,把這些共同的部分再次抽象,Iterator接口就是迭代器的頂層抽象

 

List集合中有本身特有的迭代器ListIterator,ListIterator接口是Iterator接口的子接口。實現類ArrayListLinkedList中有迭代器的具體實現(內部類)。把迭代器設計在集合的內部,這樣,迭代器就能夠輕鬆的訪問集合的成員了

private class ListItr extends Itr implements ListIterator<E>{}

而在HashMap和TreeMap的遍歷中,咱們常常將Map的鍵存到Set集合中,Set屬於List的子接口,固然也繼承了List特有的迭代器,經過迭代key來獲取value。另外一種方式是將Map中的映射關係Entry取出存儲到Set集合,經過迭代每個映射Entry關係獲取Entry中的key和value。能夠說,Map是借List的迭代器實現了對自身的遍歷

集合遍歷效率總結

l ArrayList是數組結構,ArrayList經常使用遍歷有for循環(索引訪問)、加強for循環、迭代器迭代。其中按索引訪問效率最高,其次是加強for循環,使用迭代器的效率最低。

 

(1)按索引訪問時間複雜度爲O(1),即一次定位尋址便可。

(2)查找某一個值,須要遍歷數組逐個對比,時間複雜度爲O(n),即須要對比n次。

(3)對於二分查找(元素有序是前提)時間複雜度爲O(logn)

(4)對於插入和刪除,由於涉及到元素移動的問題,時間複雜度也爲O(n)

 

l LinkedList是鏈表結構,經常使用遍歷也有for循環(索引訪問)、加強for循環、迭代器迭代。建議不要採用隨機訪問的方式去遍歷LinkedList,而採用逐個遍歷的方式。

 

1)查找某一個值,須要遍歷鏈表逐個對比,時間複雜度爲O(n)

2)對於插入、刪除等操做,因爲只須要改變先後節點之間的指針,時間複雜度爲O(1)

 

l HashMap是「數組+鏈表」的結構。其遍歷方式也不少,能夠經過keyset遍歷、經過entrySet遍歷、經過Map.values()遍歷(這種方式只能得到value)。

 

(1)若是table[i]處沒有鏈表,對於查找一次定位,時間複雜度爲O(1),對於增長、刪除,本質上也是對鏈表的增長、刪除(能夠理解table[i]處是隻有一個節點的鏈表),因此時間複雜度爲O(1)

(2)若是table[i]處有鏈表,查找時須要遍歷鏈表,時間複雜度爲O(n),增長、刪除時間複雜度爲O(1)

 

l TreeMap是二叉樹結構,能夠經過keysetentrySet等方式遍歷。

 

1)對於二叉樹的遍歷,給定某一個值,它會先和二叉樹的root節點對比,若是值比root節點大則在右子樹遍歷,不然就在左子樹遍歷,以此類推。所以,對於查找、增長、刪除等操做,都不會遍歷完整棵樹,平均複雜度均爲O(logn)

集合比較

Comparator與Comparable

TreeMap的分析和舉例中,咱們提到了TreeMap中的元素排序比較採用的是Comparator接口的compare()方法和Comparable接口的compareTo()。當元素自己不具有比較性或者具有的比較性不知足咱們的需求時,咱們就可使用Comparator接口或者Comparable接口來重寫咱們須要的比較邏輯。Comparator接口和Comparable接口實現比較的主要區別是:Comparator 是在元素外部實現的排序,這是一種策略模式,就是不改變元素自身,而用一個策略對象(自定義比較器)來改變比較行爲。而Comparable接口則須要修改要比較的元素,讓元素具備比較性,在元素內部從新修改比較行爲。看下面的兩個列子。

讓元素具備比較性

//實現Comparable接口強制讓Passenger對象具備可比性
class Passenger implements Comparable {
    private String name;
    private int age;
    
    Passenger(String name,int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public int compareTo(Object obj) {
        if(!(obj instanceof Passenger)) {
            return -1;
        }
        Passenger p = (Passenger)obj;
         System.out.println("調用compareTo比較"+ p.name + "與" + this.name);
        if(this.age> p.age) {
            return 1;
        }
        if(this.age == p.age) {
            return this.name.compareTo(p.name);
        }
        return -1;
    }
}

public class TreeSetTest {
    public static void main(String[] args) {
        TreeSet ts = new TreeSet();
         print("-----------添加元素到TreeSet----------");
        ts.add(new Passenger("zhangsan",20));
        ts.add(new Passenger("lingsi",21));
        ts.add(new Passenger("wangwu",22));
        ts.add(new Passenger("wangwu",22));
        
        Iterator it = ts.iterator();
        while(it.hasNext()) {
            Passenger obj = (Passenger)it.next();
            print(obj.getName() + "-" + obj.getAge());
        }
    }
    
    public static void print(Object obj) {
        System.out.println(obj);
    }
}

運行結果:
-----------添加元素到TreeSet----------
調用compareTo比較zhangsan與zhangsan
調用compareTo比較zhangsan與lingsi
調用compareTo比較zhangsan與wangwu
調用compareTo比較lingsi與wangwu
調用compareTo比較lingsi與wangwu
調用compareTo比較wangwu與wangwu
zhangsan-20
lingsi-21
wangwu-22
View Code

 

上面的例子中,Passenger自己並不具有比較性,可是經過修改Passenger,讓其實現了Comparable 接口重寫了compareTo()方法讓其根據年齡具備比較性。

自定義比較器

public class TreeSetTest2 {
    
    public static void main(String[] args) {
        TreeSet ts = new TreeSet(new MyComparator());
         print("-----------添加元素到TreeSet----------");
        ts.add(new Passenger("zhangsan",20));
        ts.add(new Passenger("lingsi",21));
        ts.add(new Passenger("wangwu",22));
        ts.add(new Passenger("chenliu",23));
        
        Iterator it = ts.iterator();
        while(it.hasNext()) {
            Passenger obj = (Passenger)it.next();
            print(obj.getName() + "-" + obj.getAge());
        }
    }
    
    public static void print(Object obj) {
        System.out.println(obj);
    }
}

class MyComparator implements Comparator {
    @Override
    public int compare(Object o1, Object o2) {
        if(!(o1 instanceof Passenger) || !(o2 instanceof Passenger)) {
            throw new RuntimeException("不可比較的對象");
        }
        Passenger p1 = (Passenger)o1;
        Passenger p2 = (Passenger)o2;
         System.out.println("調用compare比較"+ p1.getName() + "與" + p2.getName());
        if(p1.getAge() > p2.getAge()) {
            return 1;
        }
        if(p1.getAge() < p2.getAge()) {
            return -1;
        }
        return 0;
    }
}

運行結果:
-----------添加元素到TreeSet----------
調用compare比較zhangsan與zhangsan
調用compare比較lingsi與zhangsan
調用compare比較wangwu與zhangsan
調用compare比較wangwu與lingsi
調用compare比較chenliu與lingsi
調用compare比較chenliu與wangwu
zhangsan-20
lingsi-21
wangwu-22
View Code

從上面的例子能夠看出,Passenger自己並不具有比較性,咱們也沒有修改Passenger,而是在外部自定義一個比較器,傳入TreeSet中,使得TreeSet中存儲的Passenger具備了比較性。

集合工具類

Collections

Collections是一個工具類,提供了操做集合的經常使用方法:

 

<!--排序, 對list中元素按升序進行排序,list中的全部元素都必須實現 Comparable 接口-->
void sort(List<T> list)

<!--混排,打亂list中元素的順序-->
void shuffle(List<?> list)

<!--反轉,反轉list中元素的順序-->
void reverse(List<?> list)

<!--使用指定元素替換指定列表中的全部元素-->
void fill(List<? super T> list, T obj)

<!-- 將源list中的元素拷貝到目標list-->
void copy(List<? super T> dest, List<? extends T> src)

<!-- 返回集合中最小的元素-->
T min(Collection<? extends T> coll)

<!-- 返回集合中最大的元素-->
T max(Collection<? extends T> coll)

Collections舉例

public class CollectionsTest {

    public static void main(String[] args) {
        ArrayList al = new ArrayList();
        al.add("zhangsan");
        al.add("lisi");
        al.add("wangwu");
        
        System.out.println("集合未排序前");
        System.out.println(al);
        
        System.out.println("使用Collections.sort()方法天然排序");
        Collections.sort(al);
        System.out.println(al);
        
        System.out.println("使用Comparator按長度自定義排序");
        Collections.sort(al,new StringLengthComparator());
        System.out.println(al);
        
        System.out.println("al中最長的元素");
        System.out.println(Collections.max(al,new StringLengthComparator()));
        
        System.out.println("已排序的前提下,wangwu元素下標索引");
        System.out.println(Collections.binarySearch(al, "wangwu"));
        
        System.out.println("逆序");
        Collections.reverse(al);
        System.out.println(al);
        
    }
}

class StringLengthComparator implements Comparator<String> {

    @Override
    public int compare(String o1, String o2) {
        if(o1.length() > o2.length()) 
            return 1;
        if(o1.length() < o2.length())
            return -1;
        return 0;
    }
}

運行結果:
集合未排序前
[zhangsan, lisi, wangwu]
使用Collections.sort()方法天然排序
[lisi, wangwu, zhangsan]
使用Comparator按長度自定義排序
[lisi, wangwu, zhangsan]
al中最長的元素
zhangsan
已排序的前提下,wangwu元素下標索引
1
逆序
[zhangsan, wangwu, lisi]
View Code

Arrays

Arrays也是一個工具類,提供了操做數組以及在數組和集合之間轉換的一些經常使用方法。

/**能夠對各類類型的數組進行排序**/
void sort(Object[] a)
 
/** 數組變集合**/
<T> List<T> asList(T... a)
 
/** 數組轉成字符串**/
String toString(Object[] a)

Arrays舉例

public class ArraysTest {
    public static void main(String[] args) {
        String[] array = new String[]{"abc","def","hig"};
        System.out.println(Arrays.toString(array));

        List<String> list = Arrays.asList(array);
        System.out.println(list.contains("def"));
        //list.add("lmn");//UnsupportedOperationException
        
        int[] array2 = new int[]{1,2,3};
        List<int[]> list2 = Arrays.asList(array2);
        System.out.println(list2);
        
        Integer[] array3 = new Integer[]{1,2,3};
        List<Integer> list3 = Arrays.asList(array3);
        System.out.println(list3);
    }
}

運行結果:
[abc, def, hig]
true
[[I@36db4bcf]
[1, 2, 3]
View Code

須要注意的是,將數組變成集合後,不能使用集合的增長或者刪除方法,所以數組的長度是固定的,使用這些方法將會報UnsupportedOperationException異常。從上面的例子也能夠看出,若是數組中的元素都是對象,那麼轉換成集合時,數組中的元素就直接轉換成集合中的元素;若是數組中的元素是基本類型,那麼整個數組將做爲集合的元素存在。

小結

其實只要理解了集合體系各個主要實現類底層的數據結構類型,根據數據結構的特性,就能聯想到該集合有什麼特色,也很容易理解它們主要方法上的實現過程。文章主要介紹了經常使用的接口和實現類,還有不少沒提到,經過集合框架圖分模塊記憶,總體把握,再根據須要擴展其它沒提到的內容。

相關文章
相關標籤/搜索