目录
6、CopyOnWriteArrayList(线程安全,性能好)
1、List框架图

2、List的基本方法
以下为List接口的全部方法。在后面的具体实现类中会讲解具体的逻辑
java
public interface List<E> extends Collection<E> {
int size():获取集合大小
boolean isEmpty():判断是否为空
boolean contains(Object o):是否包含某个元素
Iterator<E> iterator():返回迭代器
Object[] toArray():集合转数组
boolean add(E e):添加元素
boolean remove(Object o):删除元素
boolean addAll(Collection c):批量添加
void clear():清空集合
E get(int index):根据下标获取元素
E set(int index, E element):根据下标修改元素
void add(int index, E element):指定位置插入
E remove(int index):根据下标删除
int indexOf(Object o):查找元素下标
List<E> subList(int from, int to):截取子集合
}
3、ArrayList(最常用的)
数据结构:数组
是否线程安全:否
成员变量:
java
// 默认初始容量,当我们在创建ArrayList时,未指定容量大小则初始容量为10
private static final int DEFAULT_CAPACITY = 10;
// 用于指定容量为 0 时的空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 用于默认大小的空数组实例(无参构造时使用)
// 将其与 EMPTY_ELEMENTDATA 区分开,是为了知道添加第一个元素时扩容到多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// ArrayList 的底层存储数组,这里可以看到ArrayList的底层数据结构是基于数组实现的
transient Object[] elementData; // non-private to simplify nested class access
// 当前数组的实际元素个数
private int size;
EMPTY_ELEMENTDATA 和DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的区别可以看后续的add方法,这里先简单说下区别:
-
EMPTY_ELEMENTDATA:new ArrayList(0) 使用,第一次扩容到 1
-
DEFAULTCAPACITY_EMPTY_ELEMENTDATA:new ArrayList() 使用,第一次扩容到 10
目的:让 JDK 区分"用户手动设为0"和"默认构造",执行不同扩容策略。
常见问题点:
1、根据add方法可以看到如果不指定初始容量,大量数据会频繁扩容,性能较差,如果我们知道长度预先指定长度可以减少扩容次数。
2、线程不安全,多线程下会出现数据丢失、越界
3、适用场景:查询快,但是增删慢。删除元素如果元素在数组中间,需要将后面所有元素前移。如果增加元素可能出发扩容。
常用方法源码解析:
public ArrayList()
ArrayList的无参构造
在我们使用ArrayList的无参构造方法时,可以看到将当前元素指定为DEFAULTCAPACITY_EMPTY_ELEMENTDATA对象,后续扩容时扩容为默认大小10

public ArrayList(int initialCapacity)
ArrayList有参构造,入参为该数组大小
java
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 如果入参值大于0,设置当前集合大小为入参值
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 如果入参值等于0,标记为EMPTY_ELEMENTDATA,后续扩容为1
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 小于0报错
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList(Collection<? extends E> c)
ArrayList有参构造,入参为顶级Collection接口
java
public ArrayList(Collection<? extends E> c) {
// 将入参赋值给elementData
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 如果入参集合长度不等于0,就使用copyOf方法复制为一个新数组赋值到elementData
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 入参集合长度等于0,标记为空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
public boolean add(E e)
常用的add方法,向集合中添加一个元素
java
public boolean add(E e) {
// 这个方法是集合进行动态扩容的关键
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
调用该方法进行判断
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
获取此时调用add方法时需要的容量大小
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果是等于无参构造中的DEFAULTCAPACITY_EMPTY_ELEMENTDATA,这里也就是体现到了DEFAULTCAPACITY_EMPTY_ELEMENTDATA和EMPTY_ELEMENTDATA的区别。
// 如果是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,说明是第一次向集合中添加元素
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 取默认值和当前数组所需容量大小的最大值返回。这里就可以看到使用了默认长度10
// 可以看到集合不是在new的时候创建容量的,而是在第一次add元素时创建容量的
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
//快速失败机制(fail-fast) 和迭代器一致性检测
modCount++;
// 这里判断是否需要扩容,如果返回的所需数组大小,大于当前数组的长度,说明容量不够用了, 就需要出发扩容方法来增加容量。
// 因此从这里可以看出来,如果我们可以指定集合存放数据的大小,就可以使用初始化时设置集合长度避免扩容。
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
实际扩容方法
oldCapacity + (oldCapacity >> 1); 从这句可以看出每次扩容是之前的1.5倍
java
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);
}
由此我们可以看到数组的扩容是由数组的拷贝实现的
public boolean remove(Object o)
通过这个源码可以发现当我们移除元素时,需要进行for循环一个一个进行比较,调用equals比较,移除掉后需要将整个数组前移,所以他的时间复杂度是O(n)
java
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
}
public boolean contains(Object o)
这里看到contains方法也是通过for循环遍历对比的,性能也较差,时间复杂度O(n)
java
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
4、Vector(线程安全,性能差)
数据结构:数组
是否线程安全:是
常用方法源码解析:
我们可以看到他的线程安全是通过synchronized实现的,所以性能较差,现在已经很少用了
java
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
5、LinkedList(链表)
数据结构:链表
是否线程安全:否
成员变量:
java
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
常见问题点
1、占用内存比ArrayList多,因为每个元素都是一个Node节点,需要存储前后节点。
2、线程不安全。
3、增删快,查询慢。增删只需要修改前后节点指针即可,查询需要遍历。
构造方法
java
public LinkedList() {
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
Node对象
java
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;
}
}
常用方法源码解析:
public boolean add(E e)
将当前要添加的元素挂在链表最后一个节点
java
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
public E get(int index)
可以看到是通过遍历获取的,性能较差
java
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
6、CopyOnWriteArrayList(线程安全,性能好)
数据结构:数组
是否线程安全:是
核心原理:读不加锁、修改时加锁复制
常见问题点
1、读:无锁,直接读当前数组
2、写:加锁 → 复制新数组 → 写完替换引用。所以性能比不加锁差。
3、数据有最终一致性,不能实时一致。因为读不加锁,有可能写正在修改时候被读导致数据不一致。
4、内存占用高(同一时间存在新旧两个数组)
代码实现
java
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();
}
}

总结比较
1、日常开发优先用 ArrayList
2、频繁头尾增删用 LinkedList
3、高并发读多写少用 CopyOnWriteArrayList
4、Vector 基本废弃
|------|-----------|------------|-----------------|----------------------|
| | ArrayList | LinkedList | Vector | CopyOnWriteArrayList |
| 数据结构 | 动态数组 | 双向链表 | 动态数组 | 写时复制数组 |
| 查询速度 | O1 | On | O1 | O1 |
| 增删速度 | On | O1 | On | On(加锁、慢) |
| 内存占用 | 小 | 大 | 小 | 高 |
| 线程安全 | 否 | 否 | 是(synchronized) | 是(ReentrantLock) |
| 扩容机制 | 1.5倍 | 无 | 2倍 | 每次复制新数组 |
| 适用场景 | 大量查询、少增删 | 频繁增删、少查询 | 基本废弃 | 读多写少 |
