这里只列出了常用的java集合。很多文章中指出了两个集合大类的差距,比如是否有序、是否可重复等等,以及什么时候应该使用那种集合。这篇文章,从源码出发,说明白为什么会有这些不同。
List
LinkedList
因为链表是可以无限加的所以不涉及到扩容
结构
双向链表,单个节点的结构如下:
private static class Node<E> {
// 储存的值
E item;
// 指向下一个节点
Node<E> next;
// 指向上一个节点
Node<E> prev;
}
插入
尾插,时间复杂度O(1)
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;
// 集合数量加1
size++;
// 修改数量加1
modCount++;
}
获取节点
遍历列表获取指定位置的元素,时间复杂度O(n)
Node<E> node(int 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;
}
}
删除节点
根据下标和根据内容删除都需要遍历链表,但是在删除节点时,只需要把头节点和尾节点相互指向即可。时间复杂度O(n)。
ArrayList
数组不想链表那样无限增加,在增加的时候可能需要扩容数组来插入新的数据。
结构
底层就是维护了一个数组,但是再插入和删除的时候为我们屏蔽了扩容和移动的操作。
transient Object[] elementData;
扩容
- 默认的容量:10
private static final int DEFAULT_CAPACITY = 10;
- 扩容量:扩容到之前的1.5倍
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 扩容为1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 创建新的数据,吧旧的数据移动到新的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
插入
如果不考虑扩容,那么时间复杂度为O(1),如果扩容,会导致数据备份,时间复杂度O(n)
public boolean add(E e) {
// 扩容
ensureCapacityInternal(size + 1);
// 把数据加到下一位
elementData[size++] = e;
return true;
}
删除
删除涉及到数据的移动,时间复杂度为O(n)
public E remove(int index) {
// 校验
rangeCheck(index);
modCount++;
// 拿到删除的数据
E oldValue = elementData(index);
// 后面的数据向前移动一位,删除最后一位的数据
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
return oldValue;
}
流程如下,红色为要删除的数据,橙色是有数据的单元,数字为单元中的数据。
获取节点
获取节点可以直接通过下表获取,时间复杂度为O(1)
Set
HashSet
HashSet的实现是基于HashMap的。这里详细的会在HashMap中提到,这里只需要大家看一下HashSet的源代码,主要使用了HashMap的key不能重复进行实现的。因为是hash计算,不考虑扩容时,插入和删除的时间复杂度都是O(1)。