ArrayList恐怕是我们在工作中使用最频繁的集合类型了
ArrayList
它的构造方法如下:
// 默认容量
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; class access
private int size;
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList() {
// 初始化一个默认的空数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
添加元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
每次添加元素都要计算
private void ensureCapacityInternal(int minCapacity) {
// 每次添加元素都要进行计算
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
计算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 第一次添加元素
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 扩容为默认容量10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return 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);
}
所以大家在选择动态数组时,如果要对存储的元素个数有一个预估,那么可以在创建ArrayList时,就使用ArrayList(int initialCapacity)构造器,从而避免频繁扩容,这也是一个比较推荐的做法,如果我没记错的话,Java开发规范也有这个推荐。
LinkedList
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;
}
}
添加元素
public boolean add(E e) {
// 添加到链表末尾
linkLast(e);
return true;
}
void linkLast(E e) {
// l指向最后一个节点
final Node<E> l = last;
// 创建一个新node为e,并且前一个node是l
final Node<E> newNode = new Node<>(l, e, null);
// last指向新创建的节点
last = newNode;
// l是空的话,新节点同时也是第一个节点
if (l == null)
first = newNode;
else
l.next = newNode;
// 元素个数++
size++;
// 修改次数++
modCount++;
}
删除某个元素
就是先遍历找到这个元素,然后调用unlink方法。
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
E unlink(Node<E> x) {
// 要被删除的元素
final E element = x.item;
// 被删除元素的下一个元素
final Node<E> next = x.next;
// 被删除元素的上一个元素
final Node<E> prev = x.prev;
// 代表被删除的是第一个节点
if (prev == null) {
// 被删除的下一个将成为第一个元素
first = next;
} else {
// 前一个节点的next应该指向被删除节点的后一个节点【断开】
prev.next = next;
// 断开与前一个节点的关系
x.prev = null;
}
// 被删除的是最后一个节点
if (next == null) {
// 前一个节点将变成last
last = prev;
} else {
// 后一个节点的prev应该指向被删除节点的前一个
next.prev = prev;
// 断开与后一个节点的关系
x.next = null;
}
// 把要删除的元素释放掉
x.item = null;
// 元素个数-1
size--;
// 修改次数+1
modCount++;
return element;
}
总体而言,两者的主要差别是顺序表和链表这两种数据结果的差别。顺序表的添加和删除是要大量元素的移动,arraylist还有额外的扩容和复制操作,而链表主要是修改前后节点的关系即可,效率更好。但是数组是通过索引访问元素的,查找效率无疑是更好的。要结合使用场景选择合适的数据结构。