基础夯实系列-集合篇(上)
基于java1.8源码分析
ArrayList
构造函数
// 可以看出,ArrayList 存储数据的方式是一个数组
transient Object[] elementData;
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
添加元素源码分析
分析都写在注释里了
public boolean add(E e) {
// 处理扩容逻辑, size 是数组中真实的数据个数
ensureCapacityInternal(size + 1);
// 在数组尾部添加元素
elementData[size++] = e;
return true;
}
// add(E e) 中的方法调用该方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 当存错数据的数组不为空时,直接返回了minCapacity,既最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 当存储数据的数组是空时,返回10 ,minCapacity 中的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// DEFAULT_CAPACITY 是 10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
// 扩容判断
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // 数组的结构被修改的次数
// 如果数组中真实元素的个数大于数组的容量,开始扩容逻辑
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
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);
}
// 中间省略了一个方法,这个是最终调用的方法
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
// 已newLength为长度的新的数组
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
// native 方法实现copy
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
####添加元素操作总结
-
初始容量。如果指定了就是指定的数值,没指定的话就是10。
-
添加操作:
- 线程不安全,时间复杂度O(1)
- 扩容。1.5倍
- 扩容后会产生新的数组。
- 可以添加空元素。且数据可重复
-
插入,删除操作不再分析源码。时间复杂度都是 O(n)
LinkedList
先看下LinkedList的继承关系
/**
* 继承了 双端队列 Deque
**/
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
}
LinkedList的插入操作源码。
// 链表节点的实体类
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 void add(int index, E element) {
// 判断索引是否越界
checkPositionIndex(index);
if (index == size)
// 如果插入的位置等于size ,直接插在链表尾部
linkLast(element);
else
// 插入到指定位置。node,linkBefore 的分析在下面
linkBefore(element, node(index));
}
// 将数据插入链表的尾部
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
// 旧的尾指针的next指向新插入的元素
l.next = newNode;
size++;
modCount++;
}
// 获取指定索引位置的数据
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;
}
}
// 将当前元素e,插入到节点succ之前
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
// 构建当前元素E的Node节点,并分配前驱,后继
final Node<E> newNode = new Node<>(pred, e, succ);
// 插入位置的节点的前驱指向新的节点
succ.prev = newNode;
if (pred == null)
first = newNode;
else
// 插入位置的前驱节点的后继指向新的节点
pred.next = newNode;
size++;
modCount++;
}
插入数据总结
- 线程不安全,时间复杂度 O(N)。分情况,如果插头,插位,时间复杂度O(1)
- 没有容量限制操作。可添加空元素,相同元素。数据可重复
- 查找复杂度O(1)
总结
- ArrayList, LinkedList 都是线程不安全的
- ArrayList基于数组,LinkedList基于双向链表
- ArrayList 查找时间复杂度低。LinkedList 插入时间复杂度低。(特殊情况除外)
- ArrayList有扩容逻辑。LinkedList没有