List集合源码分析
1.ArrayList
ArrayList是实现List接口的动态数组,允许包括 null 在内的所有元素。
每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。默认初始容量为10。随着ArrayList中元素的增加,它的容量也会不断的自动增长。
在每次添加新的元素时,ArrayList都会检查是否需要进行扩容操作,扩容操作带来数据向新数组的重新拷贝,所以如果我们知道具体业务数据量,在构造ArrayList时可以给ArrayList指定一个初始容量,这样就会减少扩容时数据的拷贝问题。
ArrayList继承AbstractList抽象父类,实现了List接口(规定了List的操作规范)、RandomAccess(可随机访问)、Cloneable(可拷贝)、Serializable(可序列化)。
ArrayList实现不是同步的。
成员属性
//默认初始容量
private static final int DEFAULT_CAPACITY = 10;
//默认空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//动态数组
transient Object[] elementData;
//数组中元素个数
private int size;
其中用于保存元素的数组elementData
加了transient
,说明该数组不直接参与序列化,而是使用writeobject
方法进行序列化,通过该方法只复制数组中有值的位置,其他未赋值的位置不进行序列化,以此节省空间。
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
int expectedModCount = modCount;
s.defaultWriteObject();
s.writeInt(size);
//根据存储元素的实际个数来序列化数组
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
初始化
如果没有指定容量,则使用默认的空数组
//初始化时创建一个空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
如果指定来初始容量,如果初始容量>0,则创建一个相应大小的数组
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 boolean add(E e) {
//第一步先给modCount加1,如果有迭代器正在迭代,则抛出异常
modCount++;
add(e, elementData, size);
return true;
}
private void add(E e, Object[] elementData, int s) {
//如果容量满了,则先扩容
if (s == elementData.length)
elementData = grow();
//将插入元素放到第一个空位上
elementData[s] = e;
size = s + 1;
}
在指定位置插入
public void add(int index, E element) {
rangeCheckForAdd(index); //检查索引是否在范围内
modCount++;
final int s;
Object[] elementData;
//如果数组已满,进行扩容
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
//把后面的元素往后挪
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
//在当前索引位置插入元素
elementData[index] = element;
size = s + 1;
}
扩容函数
private Object[] grow() {
return grow(size + 1); //传入当前元素个数+1
}
//得到需要的新容量值后,将原数组拷贝到新容量的新数组
private Object[] grow(int minCapacity) {
//通过Arrays.copyOf把原数组拷贝到新容量的新数组
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
确定新数组容量函数
private int newCapacity(int minCapacity) {
int oldCapacity = elementData.length;
//扩容1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
//进行初始化的时候,则设置新的容量为minCapacity和10中的较大值
//默认初始容量为10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
// 超出容量限制
if (minCapacity < 0)
throw new OutOfMemoryError();
//扩容容量比所需容量小时,返回所需要的容量值
return minCapacity;
}
//没超过数组最大容量,则返回需要的新容量值
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
//当新容量大于最大数组长度,有两种情况,一种是溢出,抛异常,一种是没溢出,返回整数的最大值。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE)
? Integer.MAX_VALUE
: MAX_ARRAY_SIZE;
}
1.5倍的扩容是最好的倍数。因为一次性扩容太大(例如2.5倍)可能会浪费更多的内存。但是一次性扩容太小,需要多次对数组重新分配内存,对性能消耗比较严重。所以1.5倍刚刚好,既能满足性能需求,也不会造成很大的内存消耗。
裁剪函数:把底层数组的容量调整为当前列表保存的实际元素的大小
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
查找
//遍历数组找一遍,很简单
public int indexOf(Object o) {
return indexOfRange(o, 0, size);
}
int indexOfRange(Object o, int start, int end) {
Object[] es = elementData;
if (o == null) {
for (int i = start; i < end; i++) {
if (es[i] == null) {
return i;
}
}
} else {
for (int i = start; i < end; i++) {
if (o.equals(es[i])) {
return i;
}
}
}
return -1;
}
修改
public E set(int index, E element) {
Objects.checkIndex(index, size);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
//返回数组对应位置元素,很简单
E elementData(int index) {
return (E) elementData[index];
}
删除
public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;
E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
//如果删除的不是最后一个元素,把后面的元素往前挪
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
2.Vector
初始化
//在创建时就创建一个容量为10的数组
public Vector() {
this(10);
}
增删查改
大部分与ArrayList相同,但是在方法前加了synchronized保证线程安全
public synchronized boolean add(E e) {
modCount++;
add(e, elementData, elementCount);
return true;
}
扩容
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//如果在创建Vector时,指定了capacityIncrement的大小;则,每次当Vector中动态数组容量增加时,增加的大小都是capacityIncrement
//如果容量的增量小于等于零(默认情况),则每次需要增大容量时,向量的容量将增大一倍。
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity <= 0) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
3.Stack
Stack继承Vector,他对Vector进行了简单的扩展:
Stack通过五个操作对Vector进行扩展,允许将向量视为堆栈。这个五个操作如下:
empty():测试堆栈是否为空。
peek():查看堆栈顶部的对象,但不从堆栈中移除它。
pop():移除堆栈顶部的对象,并作为此函数的值返回该对象。
push(E item):把项压入堆栈顶部。
search(Object o):返回对象在堆栈中的位置,以 1 为基数。
ArrayList和Vector的区别
1、Vector是线程安全的(效率低),ArrayList是线程非安全的(效率高)
ArrayList是线程非安全的,因为ArrayList中所有的方法都不是同步的,如果想要使用ArrayList并且让它线程安全,一个方法是用Collections.synchronizedList方法把你的ArrayList变成一个线程安全的List
2、Vector可以指定增长因子,如果该增长因子指定了,那么扩容的时候会每次新的数组大小会在原数组的大小基础上加上增长因子;如果不指定增长因子,默认的增长因子是2,而ArrayList默认的增长因子是1.5