1. 程式人生 > >Java集合(1)一 集合框架

Java集合(1)一 集合框架

希望 true ava entry treeset arrays 當前 nal 證明

目錄

Java集合(1)一 集合框架
Java集合(2)一 ArrayList 與 LinkList
Java集合(3)一 紅黑樹、TreeMap與TreeSet(上)
java集合(4)一 紅黑樹、TreeMap與TreeSet(下)
Java集合(5)一 HashMap與HashSet

引言

集合在任何語言中都是比較重要的基礎知識,不同的集合在實現上采用了各種不同的數據結構,導致了各個集合的性能以及使用方式上存在很大差異,深入了解集合框架的整體結構以及各個集合類的實現原理,並靈活使用各個集合對編碼有很大幫助。
本系列文章從集合框架的整體設計到源碼細節分析了java.util包下各個集合相關接口、抽象類以及各種常用的集合實現類,希望通過這個系列的文章對大家理解各個集合有一定幫助。

如未做特殊說明,本系列所有源碼出自以下JDK環境:

java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

接口框架

一個好的框架在設計上都會考慮將接口與實現分離,java.util也不例外。要快速理解java.util,第一步就是從他的接口設計入手,從接口的整體設計可以快速明確這個框架的作用和功能。
技術分享圖片
圖1 集合接口框架
通過上面的接口框架圖可以看出:Collection<E>和Map<K,V>是java.util框架中的兩個根接口,代表了兩種不同的數據結構:集合和映射表。而List<E>、Set<E>則是繼承自Collection<E>下最核心的兩個接口,List<E>有序可重復並可以通過整數索引來訪問,Set<E>不包含重復元素。下面我們來分別來說下這些核心接口的基本功能。

Collection<E>

public interface Collection<E> extends Iterable<E>

Collection<E>接口是集合的根接口,他代表了一組元素。但是Collection<E>並不關心這組元素是否重復,是否有序。他只提供操作對這組元素的基本操作方法,怎麽添加,怎麽刪除,怎麽循環。所有的實現類都必須提供這些方法,下面列出了Collection<E>接口的部分方法:

int size();
boolean contains(Object o);
//Returns an iterator over the elements in this collection.
Iterator<E> iterator(); //Returns an array containing all of the elements in this collection. Object[] toArray(); //Returns an array containing all of the elements in this collection; the runtime type of the returned array is that of the specified array. <T> T[] toArray(T[] a); boolean add(E e); boolean remove(Object o); void clear();

在Collection<E>接口的方法中有幾個需要註意的地方

iterator()

Iterator<E> iterator();

iterator方法返回一個實現了Iterator接口的對象,作用是依次訪問集合中的元素,Iterator<E>接口包含3個方法:

boolean hasNext();
E next();
void remove();

通過多次調用next()方法可遍歷集合中的所有元素,需要註意的是需要在調用next()之前調用hasNext()方法,並在hasNext()返回true的時候才可以調用next(),例如:

private static void collectionIterator() {
    //不用關註Arrays.asList,只需要知道他能返回一個Collection<E>接口就行
    Collection<String> collection = Arrays.asList("Java", "C++", "Python");
    Iterator<String> iterator = collection.iterator();
    while (iterator.hasNext()) {
        String string = (String) iterator.next();
        System.out.println(string);
    }
}
//output:
//Java
//C++
//Python

從JDK5開始使用“for each”這種更加方便的方式來遍歷集合,只要實現了Iterable接口,都可以使用“for each”來遍歷,效果和使用iterator一樣。Iterable接口只包含一個方法:

Iterator<E> iterator();
private static void foreachCollectionIterator() {
    Collection<String> collection = Arrays.asList("Java", "C++", "Python");
    for (String string : collection) {
        System.out.println(string);
    }
}
//output:
//Java
//C++
//Python

toArray( ) 以及 toArray(T[ ] a)

toArray和toArray(T[ ] a)返回的都是當前所有元素的數組。
toArray返回的是一個Object[]數組,類型不能改變。
toArray(T[ ] a)返回的是當前傳入的類型T的數組,更方便用戶操作,比如需要獲取一個String類型的數組:toArray(new String[0])。

List<E>

public interface List&lt;E> extends Collection&lt;E>

List<E>接口最重要的特點在有序(ordered collection)這個關鍵字上面,實現這個接口的類可以通過整數索引來訪問元素。他可以包含重復的元素。
除了包含Collection<E>接口的所有方法外,還包括跟索引有關的部分方法:

E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
List<E> subList(int fromIndex, int toIndex);

其中需要引起註意的地方是ListIterator這個類:
List<E>接口在Iterator叠代器的基礎上提供了另一個叠代器ListIterator,先來看看ListIterator<E>接口的定義:

public interface ListIterator<E> extends Iterator<E> {
    boolean hasNext();
    E next();
    boolean hasPrevious();
    E previous();
    int nextIndex();
    int previousIndex();
    void remove();
    void set(E e);
    void add(E e);
}

ListIterator<E>接口繼承自Iterator<E>接口,所以他們的差異在ListIterator<E>接口新增的功能上:

  • ListIterator<E>可以向後叠代previous()
  • ListIterator<E>可以獲取前後索引nextIndex()
  • ListIterator<E>可以添加新值add(E e)
  • ListIterator<E>可以設置新值set(E e)

Set<E>

public interface Set<E> extends Collection<E>

Set<E>接口在方法簽名上與Collection<E>接口是一樣的,只不過在方法的說明上有更嚴格的定義,最重要的特點是他拒絕添加重復元素,不能通過整數索引來訪問。Set<E>的equals方法定義如果兩個集相等是他們包含相同的元素但順序不必相同。
至於為什麽要定義一個方法簽名完全重復的接口,我的理解是為了讓框架結構更加清晰,將集合從可以添加重復元素和不可以添加重復元素,可以通過整數索引訪問和不可以通過整數索引這幾點上區別開來,這樣當程序員需要實現自己的集合時能夠更準確的繼承相應接口。

Map<K,V>

public interface Map<K,V>

API說明上關於Map<K,V>的說明非常精煉:從鍵映射到值的一個對象,鍵不能重復,每個鍵至多映射到一個值。
從鍵不能重復這個特點很容易想到通過Set<E>來實現鍵,他的接口方法Set<K> keySet()也證明了這點,下面選取了Map<K,V>接口中的一些典型方法:

int size();
boolean containsKey(Object key);
V get(Object key);
V put(K key, V value);
V remove(Object key);
void clear();
//Returns a Set view of the keys contained in this map.
Set<K> keySet();
Returns a Collection view of the values contained in this map.
Collection<V> values();
//Returns a Set view of the mappings contained in this map.
Set<Map.Entry<K, V>> entrySet();
boolean equals(Object o);
int hashCode();

java Set<K> keySet()
返回映射中包含的鍵集視圖,是一個Set<E>,說明了映射中鍵是不可重復的。
java Collection<V> values()
返回映射中包含的值得集合視圖,Collection<E>,說明了映射中值是可以重復的。
java Set<Map.Entry<K,V>> entrySet()
返回映射中包含的映射集合視圖,這個視圖是一個Set<E>,這是由他的鍵集不能重復的特點決定的。
entrySet()返回的是一個Map.Entry<K,V>類型的集,Map.Entry<K,V>接口定義了獲取鍵值、設置值的方法,定義如下:

interface Entry<K,V> {
    K getKey();
    V getValue();
    V setValue(V value);
    boolean equals(Object o);
    int hashCode();
}

類框架

從一個框架的接口知道了這個框架的結構和功能,能夠用來做什麽。但具體怎麽使用,怎麽擴展,那就需要了解框架中實現這些接口的部分。
java.util提供了一系列抽象類,來實現上面的接口,這些抽象類中提供了大量基本的方法。如果需要實現自己的集合類,擴展這些抽象類比直接繼承接口方便的多。
技術分享圖片
圖2 抽象類以及實現類框架
除了圖中的抽象類和具體實現類,還有部分歷史版本遺留下來的類,包括Vetor,Stack,Hashtable,Properties等,在這裏就不做說明,重點關註圖中的類即可。

抽象類

需要關註幾個關鍵的抽象類包括AbstractCollection<E>;、AbstractMap<K,V>、AbstractList<E>和AbstractSet<E>,從命名可以看出他們分別實現了Collection<E>、Map<K,V>、List<E>和Set<E>接口。
各個集合的關鍵區別就在每個集合所使用的數據結構和算法上,所以在抽象類層面都沒有涉及具體的數據結構和算法,只對操作這些數據結構的方法做了基本實現。

AbstractCollection<E>

public abstract class AbstractCollection<E> implements Collection<E>

AbstractCollection<E>基本實現了Collection<E>下的所有方法,除了以下幾個方法:

public abstract Iterator<E> iterator();

public abstract int size();

public boolean add(E e) {
    throw new UnsupportedOperationException();
}

如果需要實現的是一個不可修改的集合,只需要實現iterator()和size()方法即可,如果需要實現一個可修改的集合,必須重寫add(E e)方法。
在AbstractCollection<E>已經實現的方法中可以發現,AbstractCollection<E>所實現的所有方法都是通過Iterator<E>來操作的。

public boolean contains(Object o) {
    //獲取叠代器
    Iterator<E> it = iterator();
    if (o==null) {
        while (it.hasNext())
            if (it.next()==null)
                return true;
    } else {
        while (it.hasNext())
            if (o.equals(it.next()))
                return true;
    }
    return false;
}

AbstractList<E>

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>

AbstractList<E>抽象類在AbstractCollection<E>抽象類的基礎上添加了專屬於List<E>接口的部分方法,但大部分方法都是空方法,沒有具體實現。

abstract public E get(int index);

public E set(int index, E element) {
    throw new UnsupportedOperationException();
}

public void add(int index, E element) {
    throw new UnsupportedOperationException();
}

public E remove(int index) {
    throw new UnsupportedOperationException();
}

public Iterator<E> iterator() {
    return new Itr();
}

public ListIterator<E> listIterator() {
    return listIterator(0);
}

public ListIterator<E> listIterator(final int index) {
    rangeCheckForAdd(index);

    return new ListItr(index);
}

沒有實現的原因在於AbstractList<E>是一個抽象類,他並沒有確定具體的數據結構,當在數據結構沒有確定的情況下通過索引去操作一個元素,是直接使用整數索引的方式還是通過叠代器循環遍歷的方式來查找具體的位置更方便是不確定的,所以在具體實現上都由他的子類來決定。
值得註意的是AbstractList<E>實現了Iterator以及ListIterator兩種類型的叠代器:

private class Itr implements Iterator<E> {
//......
    public E next() {
        checkForComodification();
        try {
            int i = cursor;
            //向後遍歷集合,通過get(i)獲取當前索引的元素,每次調用之後cursor = i + 1,get(i)為抽象方法
            E next = get(i);
            lastRet = i;
            cursor = i + 1;
            return next;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            //通過AbstractList<E>類的remove方法來刪除元素,AbstractList<E>中remove(int index)是一個空方法,需要子類來實現
            AbstractList.this.remove(lastRet);
            if (lastRet < cursor)
                cursor--;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
    }
}
//......

private class ListItr extends Itr implements ListIterator<E> {
//......
    public E previous() {
        checkForComodification();
        try {
            int i = cursor - 1;
            //向前遍歷集合,通過get(i)獲取當前索引的元素,每次調用之前cursor - 1,get(i)為抽象方法
            E previous = get(i);
            lastRet = cursor = i;
            return previous;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }
//......
}

AbstractSet<E>

public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E>

AbstractSet<E>抽象類在實現上非常簡單,只在AbstractCollection<E>抽象類的基礎上實現了equal 和 hashCode 方法,但具體的實現還是需要通過contain()方法來判斷,由於Set<E>接口類型不考慮元素的順序,所以只要兩個AbstractSet<E>包含相同元素就判斷為相等,不需要元素順序相同,而AbstractList<E>則需要順序也相同。

//AbstractSet<E> 中的 equals
public boolean equals(Object o) {
    if (o == this)
        return true;

    if (!(o instanceof Set))
        return false;
    Collection<?> c = (Collection<?>) o;
    if (c.size() != size())
        return false;
    try {
        //containsAll在AbstractCollection<E>中已經實現,只要包含所有元素就可以
        return containsAll(c);
    } catch (ClassCastException unused)   {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }
}

//AbstractCollection<E> 中的containsAll
public boolean containsAll(Collection<?> c) {
    for (Object e : c)
        if (!contains(e))
            return false;
    return true;
}

//AbstractList<E> 中的equals
public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof List))
        return false;

    ListIterator<E> e1 = listIterator();
    ListIterator<?> e2 = ((List<?>) o).listIterator();
    //需要兩個集合中的元素以及元素順序都相同才返回true
    while (e1.hasNext() && e2.hasNext()) {
        E o1 = e1.next();
        Object o2 = e2.next();
        if (!(o1==null ? o2==null : o1.equals(o2)))
            return false;
    }
    return !(e1.hasNext() || e2.hasNext());
}

AbstractMap<K,V>

public abstract class AbstractMap<K,V> implements Map<K,V>

AbstractMap<K,V>抽象類中實現了除entrySet()方法外的基本所有方法,其中返回鍵集的Set<K> keySet()和返回值集的Collection<V> values()在實現上非常有趣,從返回值上看是創建了一個新的集合,但實際實現上是返回來一個實現Set<K>或Collection<V>的類對象,類對象的所有操作都是在原映射表的基礎上進行的,這種有趣的操作叫視圖,java.util框架中存在大量應用。
這裏使用視圖的好處在於抽象類中你不需要確定返回的Set<K>或Collection<V>的具體實現類是什麽,這樣就可以在抽象類中沒有決定使用哪種數據結構的時候最大化抽象類的功能,增加擴展的方便性。
keySet()的源碼:

public Set<K> keySet() {
    Set<K> ks = keySet;
    if (ks == null) {
        ks = new AbstractSet<K>() {
            public Iterator<K> iterator() {
                return new Iterator<K>() {
                    //獲取原映射表的叠代器來實現自己的叠代器
                    private Iterator<Entry<K,V>> i = entrySet().iterator();

                    public boolean hasNext() {
                        return i.hasNext();
                    }

                    public K next() {
                        return i.next().getKey();
                    }

                    public void remove() {
                        i.remove();
                    }
                };
            }

            public int size() {
                //直接操作原映射表的size()方法
                return AbstractMap.this.size();
            }

            public boolean isEmpty() {
                return AbstractMap.this.isEmpty();
            }

            public void clear() {
                AbstractMap.this.clear();
            }

            public boolean contains(Object k) {
                return AbstractMap.this.containsKey(k);
            }
        };
        keySet = ks;
    }
    return ks;
}

總結

java.util這個框架的結構還是非常清晰的,從接口的分類,每個接口的抽象類實現,都很好的保證了框架的伸縮性,為後續的實現和自定義擴展提供了極大地方便。
除了上述的接口以及抽象類以外,java.util框架還提供了一些其他結構,在使用頻率上不是太高,比如Queue<E> ,Deque<E> 等。
在後面的章節中我們會詳細講解幾個關鍵集合的實現,從數據結構、算法以及性能等各方面分析孰優孰劣。

Java集合(1)一 集合框架