集合在任何語言中都是比較重要的基礎知識,不一樣的集合在實現上採用了各類不一樣的數據結構,致使了各個集合的性能以及使用方式上存在很大差別,深刻了解集合框架的總體結構以及各個集合類的實現原理,並靈活使用各個集合對編碼有很大幫助。 本系列文章從集合框架的總體設計到源碼細節分析了java.util包下各個集合相關接口、抽象類以及各類經常使用的集合實現類,但願經過這個系列的文章對你們理解各個集合有必定幫助。 如未作特殊說明,本系列全部源碼出自如下JDK環境:java
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,第一步就是從他的接口設計入手,從接口的總體設計能夠快速明確這個框架的做用和功能。 算法
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<E> iterator();
複製代碼
iterator方法返回一個實現了Iterator接口的對象,做用是依次訪問集合中的元素,Iterator<E>接口包含3個方法:app
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返回的是一個Object[]數組,類型不能改變。 toArray(T[ ] a)返回的是當前傳入的類型T的數組,更方便用戶操做,好比須要獲取一個String類型的數組:toArray(new String[0])。性能
public interface List<E> extends Collection<E> 複製代碼
List<E>接口最重要的特色在有序(ordered collection)這個關鍵字上面,實現這個接口的類能夠經過整數索引來訪問元素。他能夠包含重複的元素。 除了包含Collection<E>接口的全部方法外,還包括跟索引有關的部分方法:ui
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>接口新增的功能上:
public interface Set<E> extends Collection<E> 複製代碼
Set<E>接口在方法簽名上與Collection<E>接口是同樣的,只不過在方法的說明上有更嚴格的定義,最重要的特色是他拒絕添加劇復元素,不能經過整數索引來訪問。Set<E>的equals方法定義若是兩個集相等是他們包含相同的元素但順序沒必要相同。 至於爲何要定義一個方法簽名徹底重複的接口,個人理解是爲了讓框架結構更加清晰,將集合從能夠添加劇復元素和不能夠添加劇復元素,能夠經過整數索引訪問和不能夠經過整數索引這幾點上區別開來,這樣當程序員須要實現本身的集合時可以更準確的繼承相應接口。
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提供了一系列抽象類,來實現上面的接口,這些抽象類中提供了大量基本的方法。若是須要實現本身的集合類,擴展這些抽象類比直接繼承接口方便的多。
須要關注幾個關鍵的抽象類包括AbstractCollection<E>;、AbstractMap<K,V>、AbstractList<E>和AbstractSet<E>,從命名能夠看出他們分別實現了Collection<E>、Map<K,V>、List<E>和Set<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;
}
複製代碼
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();
}
}
//......
}
複製代碼
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());
}
複製代碼
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> 等。 在後面的章節中咱們會詳細講解幾個關鍵集合的實現,從數據結構、算法以及性能等各方面分析孰優孰劣。