一、集合总体框架概述
Java集合 是java 提供的工具包(java.util.*),包含了常用的数据结构:集合、链表、队列、栈、数组、映射等。
Java集合主要可以划分为4个部分:List列表、Set集合、Map映射、工具类(Iterator迭代器、Enumeration枚举类、Arrays和Collections)、。
Java集合工具包框架图(如下):
看上面的框架图,先抓住它的主干,即Collection和Map。
1. Collection是一个接口,是高度抽象出来的集合,它包含了集合的基本操作和属性。AbstractCollection抽象类,它实现了Collection中的绝大部分函数;
Collection包含了List和Set两大分支。 AbstractList和AbstractSet都继承于AbstractCollection; 具体的List实现类继承于AbstractList,而Set的实现类则继承于AbstractSet。
(01) List是一个有序的队列,每一个元素都有它的索引。第一个元素的索引值是0。
List的实现类有LinkedList, ArrayList, Vector, Stack。
(02) Set是一个不允许有重复元素的集合。
Set的实现类有HastSet和TreeSet。HashSet依赖于HashMap,它实际上是通过HashMap实现的;TreeSet依赖于TreeMap,它实际上是通过TreeMap实现的。
2. Map是一个映射接口,即key-value键值对。
AbstractMap是个抽象类,它实现了Map接口中的大部分API。HashMap,TreeMap,WeakHashMap都是继承于AbstractMap。
Hashtable虽然继承于Dictionary,但它实现了Map接口。
接下来,再看Iterator。它是遍历集合的工具,即我们通常通过Iterator迭代器来遍历集合。我们说Collection依赖于Iterator,是因为Collection的实现类都要实现iterator()函数,返回一个Iterator对象。
ListIterator是专门为遍历List而存在的。
再看Enumeration,它是JDK 1.0引入的抽象类。作用和Iterator一样,也是遍历集合;但是Enumeration的功能要比Iterator少。在上面的框图中,Enumeration只能在Hashtable, Vector, Stack中使用。
最后,看Arrays和Collections。它们是操作数组、集合的两个工具类。
二、Collection 框架概述
public interface Iterable<T> {
Iterator<T> iterator();
// 其它省略。。。
}
1. Collection
public interface Collection<E> extends Iterable<E> {...}
Collection
Collection 接口的所有子类(直接子类和间接子类)都必须实现2种构造函数:不带参数的构造函数 和 参数为Collection的构造函数。带参数的构造函数,可以用来转换Collection的类型;
public interface Collection<E> extends Iterable<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
/**
* String[] y = x.toArray(new String[0]);
*/
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
boolean equals(Object o);
int hashCode();
}
2. List
public interface List<E> extends Collection<E> {...}
List 是一个继承于Collection的接口,即List是集合中的一种。List是有序的队列,List中的每一个元素都有一个索引;第一个元素的索引值是0,往后的元素的索引值依次+1。和Set不同,List中允许有重复的元素。
关于API方面。既然List是继承于Collection接口,它自然就包含了Collection中的全部函数接口;由于List是有序队列,它也额外的有自己的API接口。主要有“添加、删除、获取、修改指定位置的元素”、“获取List中的子队列”等。
// Collection的API
boolean add(E object)
boolean addAll(Collection<? extends E> collection)
void clear()
boolean contains(Object object)
boolean containsAll(Collection<?> collection)
boolean equals(Object object)
int hashCode()
boolean isEmpty()
Iterator<E> iterator()
boolean remove(Object object)
boolean removeAll(Collection<?> collection)
boolean retainAll(Collection<?> collection)
int size()
<T> T[] toArray(T[] array)
Object[] toArray()
// 相比与Collection,List新增的API:
void add(int location, E object)
boolean addAll(int location, Collection<? extends E> collection)
E get(int location)
int indexOf(Object object)
int lastIndexOf(Object object)
ListIterator<E> listIterator(int location)
ListIterator<E> listIterator()
E remove(int location)
E set(int location, E object)
List<E> subList(int start, int end)
3. Set
public interface Set<E> extends Collection<E> {...}
Set 是一个继承于Collection的接口,即Set也是集合中的一种。Set是没有重复元素的集合。
关于API方面。Set的API和Collection完全一样。
// Set的API
boolean add(E object)
boolean addAll(Collection<? extends E> collection)
void clear()
boolean contains(Object object)
boolean containsAll(Collection<?> collection)
boolean equals(Object object)
int hashCode()
boolean isEmpty()
Iterator<E> iterator()
boolean remove(Object object)
boolean removeAll(Collection<?> collection)
boolean retainAll(Collection<?> collection)
int size()
<T> T[] toArray(T[] array)
Object[] toArray()
4. AbstractCollection
public abstract class AbstractCollection<E> implements Collection<E> {...}
AbstractCollection 是一个抽象类,它实现了Collection中除iterator()和size()之外的函数。从而方便其它类实现Collection,比如ArrayList、LinkedList等,它们这些类想要实现Collection接口,通过继承AbstractCollection就已经实现了大部分的接口了。
5. AbstractList
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {...}
AbstractList 是一个继承于AbstractCollection,并且实现List接口的抽象类。它实现了List中除size()、get(int location)之外的函数, 从而方便其它类继承List。 另外,和AbstractCollection相比,AbstractList抽象类中,实现了iterator()接口。
6. AbstractSet
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {...}
AbstractSet 是一个继承于AbstractCollection,并且实现Set接口的抽象类。由于Set接口和Collection接口中的API完全一样,Set也就没有自己单独的API。和AbstractCollection一样,它实现了List中除iterator()和size()之外的函数。 作用:它实现了Set接口中的大部分函数。从而方便其它类实现Set接口。
7. Iterator
public interface Iterator<E> {...}
Iterator 是一个接口,它是集合的迭代器。集合可以通过Iterator去遍历集合中的元素。Iterator提供的API接口,包括:是否存在下一个元素、获取下一个元素、删除当前元素。
注意:Iterator遍历Collection时,是fail-fast机制的。即,当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。
// Iterator的API
boolean hasNext()
E next()
void remove()
8. ListIterator
public interface ListIterator<E> extends Iterator<E> {...}
ListIterator 是一个继承于Iterator的接口,它是队列迭代器。专门用于便利List,能提供向前/向后遍历。相比于Iterator,它新增了添加、是否存在上一个元素、获取上一个元素等等API接口。
// ListIterator的API
// 继承于Iterator的接口
boolean hasNext()
E next()
void remove()
// 新增API接口
void add(E object)
boolean hasPrevious()
int nextIndex()
E previous()
int previousIndex()
void set(E object)
三、ArrayList (线程不安全)
ArrayList 继承了AbstractList,实现了List,是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
RandmoAccess是java中用来被List实现,为List提供快速随机访问功能的。ArrayList 实现了RandmoAccess接口,在ArrayList中,可以通过元素的序号快速获取元素对象,这就是快速随机访问。
和Vector不同,ArrayList中的操作不是线程安全的!建议在单线程中才使用ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。
// 默认构造函数
ArrayList()
// capacity是ArrayList的默认容量大小。当由于增加数据导致容量不足时,容量会添加上一次容量大小的一半。
ArrayList(int capacity)
// 创建一个包含collection的ArrayList
ArrayList(Collection<? extends E> collection)
ArrayList包含了两个重要的对象:elementData 和 size。
(01) ArrayList 通过Object[]类型的动态数组elementData[]去保存数据的。默认容量大小是10。
(02) 当ArrayList容量不足以容纳全部元素时,ArrayList会扩容:新的容量=“(原始容量x3)/2 + 1”。
(03) ArrayList的克隆函数,即是将全部元素克隆到一个数组中。
(04) ArrayList实现Serializable的方式:当写入到输出流时,先写入“容量,再依次写入“每一个元素”;
当读出输入流时,先读取“容量”,再依次读取“每一个元素”。 size 则是动态数组的实际大小。
ArrayList支持3种遍历方式:
1. 迭代器遍历 (效率最低)
Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
value = (Integer)iter.next();
}
随机访问,通过索引值去遍历。(效率最高)(由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素)
Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
value = (Integer)list.get(i);
}
3. for 循环遍历
Integer value = null;
for (Integer integ:list) {
value = integ;
}
toArray() 异常
当我们调用ArrayList中的 toArray(),可能遇到过抛出“java.lang.ClassCastException”异常的情况。ArrayList提供了2个toArray()函数:
Object[] toArray()
<T> T[] toArray(T[] contents)
调用 toArray() 函数会抛出“java.lang.ClassCastException”异常,但是调用 toArray(T[] contents) 能正常返回 T[]。
toArray() 返回的是 Object[] 数组,将 Object[] 转换为其它类型(如,将Object[]转换为Integer[])则会抛出“java.lang.ClassCastException”异常,Java向下转型可能会发生异常。
解决该问题的办法是调用 <T> T[] toArray(T[] contents) , 而不是 Object[] toArray()。
// toArray(T[] contents)调用方式二。最常用!
public static Integer[] vectorToArray2(ArrayList<Integer> v) {
Integer[] newText = (Integer[])v.toArray(new Integer[0]);
return newText;
}
fail-fast 机制
fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;
那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。
fail-fast机制,是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。
若在多线程环境下使用fail-fast机制的集合,建议使用“java.util.concurrent包下的类”去取代“java.util包下的类”。
fail-fast机制
在调用 next() 和 remove()时,都会执行 checkForComodification()。若 “modCount 不等于 expectedModCount”,则抛出ConcurrentModificationException异常,产生fail-fast事件。
从Itr类中,我们知道 expectedModCount 在创建Itr对象时,被赋值为 modCount。通过Itr,我们知道:expectedModCount不可能被修改为不等于 modCount。所以,需要考证的就是modCount何时会被修改。无论是add()、remove(),还是clear(),只要涉及到修改集合中的元素个数时,都会改变modCount的值。 我们再系统的梳理一下fail-fast是怎么产生的。步骤如下:
(01) 新建了一个ArrayList,名称为arrayList。
(02) 向arrayList中添加内容。
(03) 新建一个“线程a”,并在“线程a”中通过Iterator反复的读取arrayList的值。
(04) 新建一个“线程b”,在“线程b”中删除arrayList中的一个“节点A”。
(05) 这时,就会产生有趣的事件了。
在某一时刻,“线程a”创建了arrayList的Iterator。此时“节点A”仍然存在于arrayList中,创建arrayList时,expectedModCount = modCount(假设它们此时的值为N)。在“线程a”在遍历arrayList过程中的某一时刻,“线程b”执行了,并且“线程b”删除了arrayList中的“节点A”。“线程b”执行remove()进行删除操作时,在remove()中执行了“modCount++”,此时modCount变成了N+1!“线程a” 接着遍历,当它执行到next()函数时,调用checkForComodification()比较“expectedModCount”和“modCount”的大小;而“expectedModCount=N”,“modCount=N+1”,这样,便抛出ConcurrentModificationException异常,产生fail-fast事件。 即: 当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值);这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。
java.util.concurrent包中是如何解决fail-fast事件的:
(01) 和ArrayList继承于AbstractList不同,CopyOnWriteArrayList 没有继承于AbstractList,它仅仅只是实现了List接口。
(02) ArrayList的iterator()函数返回的Iterator是在AbstractList中实现的;而CopyOnWriteArrayList是自己实现Iterator。
(03) ArrayList的Iterator实现类中调用next()时,会“调用checkForComodification()比较‘expectedModCount’和‘modCount’的大小”;
但是,CopyOnWriteArrayList的Iterator实现类中,没有所谓的checkForComodification(),更不会抛出ConcurrentModificationException异常!
四、LinkedList (线程不安全)
java.lang.Object
↳ java.util.AbstractCollection<E>
↳ java.util.AbstractList<E>
↳ java.util.AbstractSequentialList<E>
↳ java.util.LinkedList<E>
public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {...}
LinkedList 是继承于AbstractSequentialList的双向链表,也可以被当作堆栈、队列或双端队列进行操作。
LinkedList 实现 List
LinkedList 实现 Deque
// 默认构造函数
LinkedList()
// 创建一个LinkedList,保护Collection中的全部元素。
LinkedList(Collection<? extends E> collection)
LinkedList是AbstractSequentialList的子类。
AbstractSequentialList 实现了get(int index)、set(int index, E element)、add(int index, E element) 和
remove(int index)这些函数。这些接口都是随机访问List的;
LinkedList的本质是双向链表。
(01) LinkedList继承于AbstractSequentialList,并且实现了Deque接口。
(02) LinkedList包含两个重要的成员:header 和 size。
header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。
Entry中包含成员变量: previous, next, element。 size是双向链表中节点的个数。
LinkedList实际上是通过双向链表去实现的,它的 顺序访问非常高效,而随机访问效率比较低。
LinkedList也实现了List接口{即它实现了get(int location)、remove(int location)等“根据索引值来获取、删除节点的函数”}。LinkedList如何实现List的这些接口的,如何将“双向链表和索引值联系起来的"?
实际原理非常简单,它就是通过一个计数索引值来实现的。例如,当我们调用get(int location)时,首先会比较“location”和“双向链表长度的1/2”;若前者大,则从链表头开始往后查找,直到location位置;否则,从链表末尾开始先前查找,直到location位置。
总结:
(01) LinkedList
它包含一个非常重要的内部类:Entry。Entry是双向链表节点所对应的数据结构,它包括的属性有:previous, next, element
(02) 从LinkedList的实现方式中可以发现,它不存在LinkedList容量不足的问题。
(03) LinkedList的克隆函数,即是将全部元素克隆到一个新的LinkedList对象中。
(04) LinkedList实现java.io.Serializable。当写入到输出流时,先写入“容量”,再依次写入“每一个节点保护的值”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。
(05) 由于LinkedList实现了Deque,而Deque接口定义了在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。
第一个元素(头部) 最后一个元素(尾部)
抛出异常 特殊值 抛出异常 特殊值
插入 addFirst(e) offerFirst(e) addLast(e) offerLast(e)
移除 removeFirst() pollFirst() removeLast() pollLast()
检查 getFirst() peekFirst() getLast() peekLast()
// 返回第一个节点
// 若LinkedList的大小为0,则返回null
public E peekFirst() {
if (size==0)
return null;
return getFirst();
}
// 获取LinkedList的第一个元素
public E getFirst() {
if (size==0)
throw new NoSuchElementException();
// 链表的表头header中不包含数据。
// 这里返回header所指下一个节点所包含的数据。
return header.next.element;
}
(06) LinkedList可以作为FIFO(先进先出)的队列,作为FIFO的队列时,下表的方法等价:
队列方法 等效方法
add(e) addLast(e)
offer(e) offerLast(e)
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()
(07) LinkedList可以作为LIFO(后进先出)的栈,作为LIFO的栈时,下表的方法等价:
栈方法 等效方法
push(e) addFirst(e)
pop() removeFirst()
peek() peekFirst()
LinkedList 遍历方式
LinkedList支持多种遍历方式(1.通过Iterator去遍历 2. 快速随机访问遍历 3. foreach循环 4.其它 pollFirst() 之类)。千万不要采用随机访问的方式去遍历LinkedList,而采用逐个遍历的方式。
# 遍历 ArrayList (实现了RandomAccess接口):
for (int i=0; i<list.size(); i++) {
list.get(i);
}
# 遍历 LinkedList :
for (Integer integ:list)
;
五、 Vector (线程安全)
Vector 是矢量队列,继承于AbstractList,实现了List, RandomAccess, Cloneable这些接口。Vector 继承了AbstractList,实现了List;是一个队列,支持相关的添加、删除、修改、遍历等功能。Vector 实现了RandmoAccess接口,即提供了随机访问功能。
和ArrayList不同,Vector中的操作是线程安全的。 方法使用 synchronized 关键字修饰;
Vector的数据结构和ArrayList差不多,有3个成员变量:elementData , elementCount, capacityIncrement。
(01) elementData 是"Object[]型的数组",可动态扩展,默认为10,它保存了添加到Vector中的元素。
(02) elementCount 是动态数组的实际大小。
(03) capacityIncrement 是动态数组的增长系数。
int newCapacity = (capacityIncrement > 0) ? (oldCapacity + capacityIncrement) : (oldCapacity * 2);
(04) Vector的克隆函数,即是将全部元素克隆到一个数组中。
# Vector 遍历方式 (推荐:序号随机访问)
Integer value = null;
int size = vec.size();
for (int i=0; i<size; i++) {
value = (Integer)vec.get(i);
}
Vector 和 ArrayList 的比较:
1. Vector是多线程安全的,而ArrayList不是,Vector类中的方法很多有synchronized进行修饰,导致了Vector效率无法与ArrayList相比;
2. 两个都是采用的线性连续空间(数组)存储元素,但是当空间不足的时候,两个类的增加方式是不同的。
ArrayList会扩容:新的容量=“(原始容量x3)/2 + 1”。
Vector扩容: int newCapacity = (capacityIncrement > 0) ? (oldCapacity + capacityIncrement) : (oldCapacity * 2);
3. Vector可以设置增长因子(Vector中的 capacityIncrement),而ArrayList不可以;
六、Stack
public class Stack<E> extends Vector<E> {...}
java工具包中的Stack是继承于Vector(矢量队列)的,由于Vector是通过数组实现的,因此,Stack也是通过数组实现的,而非链表。当然,我们也可以将 LinkedList 当作栈来使用!
// Stack 的 API
boolean empty()
synchronized E peek()
synchronized E pop()
E push(E object)
synchronized int search(Object o)
(01) Stack实际上也是通过数组去实现的。
执行push时(即,将元素推入栈中),是通过将元素追加的数组的末尾中。
执行peek时(即,取出栈顶元素,不执行删除),是返回数组末尾的元素。
执行pop时(即,取出栈顶元素,并将该元素从栈中删除),是取出数组末尾的元素,然后将该元素从数组中删除。
(02) Stack继承于Vector,意味着Vector拥有的属性和功能,Stack都拥有。
List总结(LinkedList, ArrayList等使用场景和性能分析):
(01) List 是一个接口,它继承于Collection的接口。它代表着有序的队列。
(02) AbstractList 是一个抽象类,它继承于AbstractCollection。AbstractList实现List接口中除size()、get(int location)之外的函数。
(03) AbstractSequentialList 是一个抽象类,它继承于AbstractList。AbstractSequentialList 实现了“链表中,根据index索引值操作链表的全部函数”。
(04) ArrayList, LinkedList, Vector, Stack是List的4个实现类。
ArrayList 是一个数组队列,相当于动态数组,随机访问效率高,随机插入、随机删除效率低。
LinkedList 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率低。
Vector 是矢量队列,和ArrayList一样,它也是一个动态数组。但是ArrayList是非线程安全的,而Vector是线程安全的。
Stack 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。
涉及到“栈”、“队列”、“链表”等操作,应该考虑用List:
(01) 对于需要快速插入,删除元素,应该使用LinkedList。
(02) 对于需要快速随机访问元素,应该使用ArrayList。
(03) 对于“单线程环境” 或者 “多线程环境,但List仅仅只会被单个线程操作”,此时应该使用非同步的类(如ArrayList)。
对于“多线程环境,且List可能同时被多个线程操作”,此时,应该使用同步的类(如Vector)。