集合在任何语言中都是比较重要的基础知识,不一样的集合在实现上采用了各类不一样的数据结构,致使了各个集合的性能以及使用方式上存在很大差别,深刻了解集合框架的总体结构以及各个集合类的实现原理,并灵活使用各个集合对编码有很大帮助。 本系列文章从集合框架的总体设计到源码细节分析了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> 等。 在后面的章节中咱们会详细讲解几个关键集合的实现,从数据结构、算法以及性能等各方面分析孰优孰劣。