ArrayList
概述:
ArrayList基于数组实现,是个动态数组,容量能够自动增长。
ArrayList实现了List接口,RandomAccess接口(通过下标序号进行快速访问),Cloneable接口和序列化接口,源代码如下:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{...}
ArrayList是非线程安全的, 多线程下使用可以考虑用Collections.synchronizedList(List list)获取线程安全的对象,或者使用concurrent并发包下的CopyOnWriteArrayList对象。
Collections.synchronizedList(List list, Object mutex)实际上是使用传入的list对象构建一个线程安全的对象:
public static <T> List<T> synchronizedList(List<T> list, Object mutex) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
SynchronizedRandomAccessList/SynchronizedList中使用synchronized修饰词对各方法进行了同步。
可以选择传入锁对象,如果不传,会创建默认锁。
创建:
ArrayList提供了三种方式的构造器:
ArrayList(int initialCapacity);
ArrayList();
ArrayList(Collection<? extends E> c)
如果使用第二种方式,会创建一个容量为0的数组,并在第一次新增元素时初始化为默认容量10。
修改:
ArrayList提供了set(int index, E element),add(int index, E element),addAll(int index, Collection<? extends E> c),remove(int index),remove(Object o),removeAll(Collection<?> c)等方法(部分参数可以省略)用于修改列表内容。
对列表进行修改时,由于数组容量大小是固定的,所以要检查是否会越界。以add方法为例,源代码如下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ensureCapacityInternal(size + 1)用于确保不会出现数组越界的情况,在需要的情况下对数组进行扩容。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
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);
}
grow方法就是对数组进行扩容的实现。可以看到,扩容方式为newCapacity = 1.5oldCapacity,即每次新增原容量的一半。
这种扩容操作需要将旧的数据全部copy至扩容后的数组中,所以代价非常高,因此应该尽量避免数组的扩容,在创建ArrayList时,指定数组的大小。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。
删除时,需要对数组进行大量的操作,以remove为例
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; // clear to let GC do its work
return oldValue;
}
其中rangeCheck(index)检查index是否越界,modCount用于记录已从结构上修改 此列表的次数,在迭代期间面临并发修改时,它提供了快速失败行为,而不是非确定性行为。 System.arraycopy(elementData, index+1, elementData, index,numMoved)实现数组的复制,该方法支持对自身的复制。由于非末尾位置的增删操作会有大量的数组拷贝工作,所以对ArrayList的非末尾增删操作效率都较低。
remove(Object o)中会用到fastRemove(int index)方法,其与remove(int index)的区别在于吧不需要对index进行验证,另外还不需要返回旧数据。
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
}
遍历:
获取数组长度根据下标序号遍历即可。或者使用迭代器。
ArrayList与Ventor
相同点:两者都是由数组实现。
不同点:ArrayList是非线程安全的,Vector是线程安全的;ArrayList扩容默认是原容量1.5倍,Vector扩容默认是原容量2倍。由于Vector的所有方法都由synchronized进行同步,所以效率会低。
LinkedList
概述:
LinkedList是基于列表实现的List,实现了List接口、双端队列Deque接口,Cloneable接口,序列化接口。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList是非线程安全集合。
LinkedList包含三个属性:size(数量),first(头元素),last(尾元素)
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
Node对象为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;
}
}
由于LinkedList是由链表实现的,所以对于列表的增删元素效率较高,但是查询时需要遍历列表,效率不高。此外,由于实现了Deque接口,所以LinkedList拥有双端队列的功能。
遍历:
构建Iterator()对象进行遍历。不要使用index进行遍历。
ArrayList与LinkedList的异同
相同点:都实现了List接口,都允许其中元素为null,列表内元素按照插入顺序排序。都是非线程安全的。
不同点:前者以数组实现,支持快速访问,后者以链表实现,不支持快速访问。所以前者按下标查询速度快,后者较慢,遍历查询时差不多。非结尾增删数据时,后者更加高效。
对列表进行遍历时,ArrayList推荐使用get(index)方法进行遍历,而LinkedList一定要使用Iterator方式进行遍历。