List学习
List接口继承了collection接口,其常用的实现类有ArrayList、Vector、LinkedList、CopyOnWriteArrayList
一、ArrayList
特性
1、ArrayList类实现了可变的数组,允许保存所有元素,包括null。
2、可以根据索引位置对集合进行快速的随机访问,查询时间复杂度为O(1)。
3、向指定位置插入或删除对象时速度较慢,时间复杂度为O(n),因为数组在内中的存储是连续的,插入或删除,需要移动元素。
4、线程不安全
底层实现原理
ArrayList的创建,常用两种方式
1、无参构造方法,创建一个默认capacity为10的Object数组
private static final int DEFAULT_CAPACITY = 10;//默认初始化容量
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//一个默认容量且为空的Object数组
transient Object[] elementData; //ArrayList的底层数据结构,一个Object数组
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {//无参构造方法
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2、有参构造方法,参数为一个int整形,用于自定义数组的容量
private static final Object[] EMPTY_ELEMENTDATA = {};//一个空的数组List
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {//当initialCapacity大于0时,创建自定义容量数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//当initialCapacity等于0时,则将EMPTY_ELEMENTDATA空列表赋值给elementData
this.elementData = EMPTY_ELEMENTDATA;
} else {//当小于0时则抛出非法参数异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
ArrayList中添加对象
//数组的大小
private int size;
//添加对象
public boolean add(E e) {
ensureCapacityInternal(size + 1); //确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
elementData[size++] = e;//向数组中添加对象
return true;
}
ArrayList的扩容机制
当添加对象时,add(E e)方法内部调用了ensureCapacityInternal(size + 1)法以确保容量足够,执行以下方法来判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)//如果minCapacity 大于当前数组长度则进入扩容
grow(minCapacity);
}
通过Arrays.copyOf(T[] original, int newLength)方法,复制原数组来实现扩容操作
//扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//原数组容量
int newCapacity = oldCapacity + (oldCapacity >> 1);//oldCapacity>>1为oldCapacity/2,新的数组扩容量为原数组的1.5倍
if (newCapacity - minCapacity < 0)//如果扩容后还不满足最小容量要求,则将最小容量赋值给newCapacity
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)//如果新newCapacity大于MAX_ARRAY_SIZE,则newCapacity赋值为Integer.MAX_VALUE
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//复制出新的容量的数组,并赋值给自己elementData
}
二、Vector
特性
1、实现原理与ArrayList相同。
1、线程安全的,因其方法是被synchronized修饰的,线程同步方法
2、因同步锁的原因,效率比ArrayList低
底层实现原理
Vector的创建
其三个构造方法其实都是调用:public Vector(int initialCapacity, int capacityIncrement)
//1.无参
public Vector() {
this(10);//默认初始容量玉arraylist相同10,调用构造方法2
}
//2.一个参数
public Vector(int initialCapacity) {
this(initialCapacity, 0);//自定义的初始化容量,0为扩容比例,调用构造方法3
}
//3.两个参数
public Vector(int initialCapacity, int capacityIncrement) {//自定义初始化容量,及扩容比例
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];//创建Object数组
this.capacityIncrement = capacityIncrement;//定义扩容比例
}
Vector的扩容机制
方式与ArrayList一样数组复制方式,默认扩容2倍
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);//当capacityIncrement大于0时,为自定义扩容比例;当capacityIncrement<=0时扩容比例为2倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
三、LinkedList
特性
1、采用链表结构保存对象,插入和删除效率较高,因为链表每个节点为单个对象,插入和删除只需要修改节点上的指向,不需要像数组似的还要移动其他元素。
2、查询效率比较低,因为需要遍历来查找
底层实现原理
底层实现是一个双向链表,数据结构为LinkedList的一个静态内部类Node节点类,该节点储存了三个内容:数据的信息、前一个节点的指向、下一个节点的指向
//链表中节点个数
transient int size = 0;
//链表中第一个节点
transient Node<E> first;
//链表中最后一个节点
transient Node<E> last;
//链表的的元素节点对象,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;
}
}
查询效率慢的原因,get(index)查询需要从头或从尾遍历到该下标位置
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {//如果index小于size的一半,则从第一个元素向后遍历
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;
}
}
四、CopyOnWriteArrayList
特性
1、与Vector不同,使用的是ReentrantLock锁
2、效率要比Vector高,添加删除上锁,get不加锁
3、底层也是数组,先复制一个length+1的数组在添加
底层实现原理
先来看下它的 add 方法源码:
添加元素时,先加锁,再进行复制替换操作,最后再释放锁
public boolean add(E e) {
//加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//获取原数组
Object[] elements = getArray();
int len = elements.length;
//复制一个长度为length+1的新数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
//在新数组中添加元素
newElements[len] = e;
//set替换原始集合为新集合
setArray(newElements);
return true;
} finally {
//释放锁
lock.unlock();
}
}
再看看get方法:
并没有加锁操作
private E get(Object[] a, int index) {
return (E) a[index];
}
public E get(int index) {
return get(getArray(), index);
}
从源码中,看到add操作中使用了重入锁,添加值的操作并不是直接在原有数组中完成,而是复制原始数组,然后将值插入到新的数组中,最后用新数组替换原数组。使用这种方式,在add的过程中旧数组没有得到修改,因此写入操作不影响读取操,另外,数组定义private transient volatile Object[] array,其中用volatile修饰,保证内存可见性,读取线程可以马上知道这个修改。
五、总结
1、ArrayList查找效率更高,尾部添加效率也还可以,但是指向索引位置的插入及删除效率比较低。
2、LinkedList执行插入及删除操作室效率高,但是查找的效率比较低。
3、ArrayList和LinkedList都是非线程安全的。
4、Vector是线程安全的,方法被synchronized修饰的,线程同步方法。
5、Vector的底层都是数组,默认初始化容量都是10,ArrayList的扩容为1.5倍,Vector默认扩容为2倍且可以自定义扩容数量。
6、CopyOnWriteArrayList线程安全的,添加时先加锁,复制后添加,再替换原来的集合,读取不加锁,性能优于Vector