目录
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,第一步就是从他的接口设计入手,从接口的整体设计可以快速明确这个框架的作用和功能。
通过上面的接口框架图可以看出: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<E> extends Collection<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提供了一系列抽象类,来实现上面的接口,这些抽象类中提供了大量基本的方法。如果需要实现自己的集合类,扩展这些抽象类比直接继承接口方便的多。
除了图中的抽象类和具体实现类,还有部分历史版本遗留下来的类,包括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> 等。
在后面的章节中我们会详细讲解几个关键集合的实现,从数据结构、算法以及性能等各方面分析孰优孰劣。