概述
List可能是我们工作中用到的最多的类,我们来看看相关子类的特性
ArrayList
我们来看看ArrayList里面关键的代码
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
# 初始化不写容器大小的话,第一次扩容的大小,刚创建的时候是0
private static final int DEFAULT_CAPACITY = 10;
# 装容器的数据
transient Object[] elementData
# 添加元素
public boolean add(E e) {
# 确保容器大小够装得下
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
# 确保容器能装得下
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
扩容策略
首先扩容一半 newCapacity = oldCapacity + (oldCapacity >> 1); 由移一位等于除2
如果还是没有需要扩容的数据大,那么直接扩容到需要扩容的大小
如果还不行,扩到 Int最大值,基本上用不上
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
}
# 删除元素 null 也可以删除, 只能删除一个
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
}
需要注意的是两个方法
Arrays.copyOf(elementData, newCapacity);通过旧数组拷贝出一个新的数组,通常用于扩容System.arraycopy()用于两个数组之间的拷贝 通过源码的阅读可以发现,基于底层是连续数组的特性,总结几个特点- 根据下标是在尾部,都需要挪动一部分数组 System.arraycopy 实现
- 根据下标访问的时候,时间复杂度是 O(1)
- 删除元素的时候,时间复杂度是 O(N)
LinkedList
顾名思义,底层是通过链表来实现
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
# 头指针
transient Node<E> first;
# 尾指针
transient Node<E> last;
# LinkedList 里面存储的全部都是 Node
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
}
链表没有扩容的需求,直接通过指针往后指就可以了。基于链表的特性,可以总结出 LinkedList 的特性
- 增加和删除元素的时候,如果是头尾添加则时间复杂度是 O(1),如果是按照下标来添加的话,时间复杂度是 O(n/2). 代码会判断距离头近还是尾近,开始遍历
- 根据下标访问的时候,时间复杂度是 O(N)
- 根据元素删除的时候,时间复杂度也是O(N)
ArrayList 和 LinkedList 对比
对比了两个类的增加删除的特性,数组只有在增加和删除的时候需要挪动数组,但是同时链表也需要遍历元素,所以差不多,而其他方面ArrayList效率都远高于LinkedList。在不考虑内存的情况下,LinkedList可以不用了。
Vector
一个所有方法都被用了 Synchronized 关键字的 ArrayList
CopyOnWriteArrayList
ArrayList 虽然好用,但是它是非线程安全的。线程安全的 Vector又显得有一些笨重。于是 JUC包里面提供了一个类 CopyOnWriteArrayList来实现线程安全的快速读的List。 先看代码,因为他保证了线程安全,所以着重看添加方法
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
# 锁
final transient ReentrantLock lock = new ReentrantLock();
# 容器
private transient volatile Object[] array
# 读操作完全不用上锁
private E get(Object[] a, int index) {
return (E) a[index];
}
public E get(int index) {
return get(getArray(), index);
}
添加数据的时候 Arrays.copyOf(elements, len + 1); 拷贝出一个新的数据,然后添加上去,最后覆盖原来的值
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
}
可以从类名就看出来这个类到底在干什么,写时复制。就是为了保证并发速度,采用了空间换时间的思想。在写的时候拷贝了一个新的数组,同时,用户读取的还是旧的数据,写好了再进行覆盖。