1、Collection
- List,有序集合,它提供了方便的访问、插入、删除等操作。
- Set,不允许重复元素的,这是和 List 最明显的区别,也就是不存在两个对象 equals 返回 true。
- Queue/Deque,则是 Java 提供的标准队列结构的实现,除了集合的基本功能,它还支持类似【先入先出(FIFO, First-in-First-Out)】或者【后入先出(LIFO,Last-In-First-Out)】等特定行为。
1-1、ArrayList
数组结构的列表,非线程安全;
1-2、LinkedList
链表结构的列表,非线程安全;
1-3、Vector
数组结构的列表,线程安全;
1-4、ArrayList与LinkedList区别
Vector 是 【线程安全(方法使用synchronized修饰)】的动态列表。Vector 内部是使用【对象数组】来保存数据,可以根据需要自动的增加容量,当数组已满时,会创建新的数组,并拷贝原有数组数据。扩容时会提高 1 倍。
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// capacityIncrement默认为0,所以扩容时时提高1倍
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
ArrayList 是应用更加广泛的动态数组实现,它本身不是线程安全的,所以性能要好很多。与 Vector 近似,ArrayList 也是可以根据需要调整容量是增加 50%。
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// oldCapacity 向右位移1位,代表除2,扩容0.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
LinkedList 是 Java 提供的【双向链表】,所以它不需要像上面两种那样调整容量,它也不是线程安全的。
适用的场景:
- Vector 和 ArrayList 作为【动态数组】,其内部元素以【数组】形式【顺序存储】的,所以非常适合随机访问的场合。除了尾部插入和删除元素,往往性能会相对较差,比如我们在中间位置插入一个元素,需要移动后续所有元素。
- LinkedList 进行节点插入、删除却要高效得多,但是【随机访问】性能则要比【动态数组】慢。
类
数据结构
扩容
线程安全
插入/删除时间复杂度
访问时间复杂度
Vector
数组
1倍
不安全
O(n)
O(1)
数组随机访问
ArrayList
数组
0.5倍
不安全
O(n)
O(1)
数组随机访问
LinkedList
双向链表
无需扩容
不安全
O(1)
O(n)
1-5、Set
- TreeSet 支持自然顺序访问,但是添加、删除、包含等操作要相对低效(log(n) 时间)。
- HashSet 则是利用哈希算法,理想情况下,如果哈希散列正常,可以提供常数时间的添加、删除、包含等操作,但是它不保证有序。
- LinkedHashSet,内部构建了一个记录插入顺序的双向链表,因此提供了按照插入顺序遍历的能力,与此同时,也保证了常数时间的添加、删除、包含等操作,这些操作性能略低于 HashSet,因为需要维护链表的开销。
在遍历元素时,HashSet 性能受自身容量影响,所以初始化时,除非有必要,不然不要将其背后的 HashMap 容量设置过大。而对于 LinkedHashSet,由于其内部链表提供的方便,遍历性能只和元素多少有关系。
1-6、ArrayList如何实现【线程安全】?
java.util.Collections 的工具类内部的【synchronizedList(List list)】方法实现ArrayList线程安全,将每个基本方法,比如 get、set、add 之类,都通过 synchronized 添加基本的同步支持,非常简单粗暴,但也非常实用。
注意这些方法创建的线程安全集合,都符合迭代时【fail-fast 行为】,当发生意外的并发修改时,尽早抛出【ConcurrentModificationException】异常,以避免不可预计的行为。
public static <T> Collection<T>
synchronizedCollection(Collection<T> c) {
return new SynchronizedCollection<>(c);
}
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {return list.equals(o);}
}
public E get(int index) {
synchronized (mutex) {
return list.get(index);
}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
1-7、ConcurrentModificationException
根据ConcurrentModificationException的文档介绍,**一些对象【不允许并发修改】,**当这些修改行为被检测到时,就会抛出这个异常。(例如一些集合不允许一个线程一边遍历时,另一个线程去修改这个集合)。
集合(例如Collection, Vector, ArrayList, LinkedList, HashSet, Hashtable, TreeMap, AbstractList)的【Iterator】实现中,如果提供这种【并发修改异常检测】,那么这些Iterator可以称为是"fail-fast Iterator",意思是【快速失败】迭代器,就是检测到并发修改时,直接抛出异常,而不是继续执行,等到获取到一些错误值时在抛出异常。
异常检测主要是通过【modCount】和【expectedModCount】两个变量来实现
modCount 集合被修改的次数,一般是被集合(ArrayList之类的)持有,每次调用add(),remove()方法会导致 modCount+1;
expectedModCount 期待的修改次数,一般是被Iterator(ArrayList.iterator()方法返回的iterator对象)持有,一般在Iterator初始化时会赋初始值,在调用Iterator的remove()方法时会对expectedModCount进行更新。
然后在Iterator调用next()遍历元素时,
会调用 checkForComodification() 方法
比较 modCount 和 expectedModCount,
不一致就抛出 ConcurrentModificationException。
单线程操作Iterator不当时也会抛出ConcurrentModificationException异常。(上面的例子就是);
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
总结
因为ArrayList和HashMap的Iterator都是上面所说的**“fail-fast Iterator”**,Iterator在获取下一个元素,删除元素时,都会比较expectedModCount和modCount,不一致就会抛出异常。
所以当【使用Iterator遍历】元素(for-each遍历底层实现也是Iterator)时,需要删除元素,一定需要使用 Iterator的remove()方法 来删除,而不是直接调用ArrayList或HashMap自身的remove()方法,否则会导致Iterator中的expectedModCount没有及时更新,之后获取下一个元素或者删除元素时,expectedModCount和modCount不一致,然后抛出ConcurrentModificationException异常。
1-7-1、迭代器与ConcurrentModificationException
目的:在【并发环境】下容器被修改是快速失败,保证容器安全和数据一致;
实现方式:计数器,记录容器变化的次数,如果在【迭代期间】计数器被修改,hasNext或next将抛出 ConcurrentModificationException。
在设计【同步容器类】的迭代器时,并没有考虑到【并发修改】问题,表现出的行为是【及时失败(fail-fast)】。容器在迭代过程被修改时,就会抛出ConcurrentModificationException异常。
1-8、集合框架中 fast-fail 和 fast-safe 设计
Java1.0 - Java1.4 集合框架设计是 fast-fail
Java1.5 开始,增加 java.util.concurrent ,增加并发安全框架 fast-safe