继承结构
我们都知道数组定义了长度就不可以改变了,而List其实就是可延长的数组,内部就是采用数组结构来实现的,具体怎么实现的,我们往下来看源码,首先是ArrayList 的继承结构如下:
构造方法
private transient Object[] elementData;
private int size;
首先我们从ArrayList的构造方法来看
我们可以看到super()调用父类AbstractList的构造方法,而AbstractList方法是没有任何内容的所以不需要管,
然后初始化10个长度的数组。
如果初始化传入了集合,则直接集合转为数组,如果转换的对象类型不一致,则重新复制数组到新数组,这里因为toArray()如果被重写,那么返回类型不会直接是Object[].
扩容实现原理
上面分析了 ArrayList 的构造器,但 ArrayList 如何做到动态扩容呢?
我们可以从 add() 方法着手进行分析(addAll() 方法类似,不再单独分析),如下:
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
可以看到,如果扩容后的长度小于原来长度,则直接返回原来的数组长度
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
但是如果扩容后的长度大于MAX_ARRAY_SIZE(2147483639)的长度,则直接分配系统默认最大的数值2147483647
最后通过底层调用Arrays.copyOf[ System.arraycopy]方法复制数组
由此可以看出,新容量为原容量的 1.5 倍;若扩容为 1.5 倍后,仍未达到所需容量,则直接使用所需要的容量,如果还未达到容量,则使用设置的默认值容量。
集合删除
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);
//删除数组最后的一位元素,给gc自动回收
elementData[--size] = null; // Let gc do its work
return oldValue;
}
小结: 集合删除元素通过把数组的元素通过System.arraycopy复制功能往前移动一位,最后进行数组最后一位清空,gc自动回收,缺点:效率低,数组重新复制。
复制方法包括: System.arraycopy , Arrays.copyof也是调用System.arraycopy, clone,for循环复制,效率最高的是System.arraycopy调用了jvm的底层。
集合查询
public E get(int index) {
//检查位置
rangeCheck(index);
//直接通过数组下标获取
return elementData(index);
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
小结:之所以ArrayList的速度快,是因为直接通过数组下标获取数据。
线程安全性
线程安全可以简单理解为:多个线程同时操作一个方法或变量时,不会出现问题;若出现问题,可认为是线程不安全的。
ArrayList 是线程不安全的,主要体现有二:
1、多个线程往 ArrayList 添加数据时(扩容时),可能会产生数组越界异常(ArrayIndexOutOfBoundsException);
如果初始化长度为5的数组,添加一个元素,第一个线程拿到5进行扩容,第二个线程拿到了6而不进行扩容,就会出现下标异常。
2、线程遍历同一个 ArrayList,有线程对其进行修改时,可能会抛出 ConcurrentModificationException。
第一个线程进行遍历,而第二个线程进行删除,第一个线程遍历的对象出现了问题,然后就会抛出ConcurrentModificationException异常
如果需要把ArrayList变成线程安全的,
1.可通过 Collections.synchronizedList(list)方法实现,它会自动将我们的list方法进行改变,最后返回给我们一个加锁了List。
2.可使用Vector集合,速度慢,但是线程安全,方法都经过Sync同步过。
基本的API
boolean | add(E e) 将指定的元素添加到此列表的尾部。 | |
void | add(int index, E element) 将指定的元素插入此列表中的指定位置。 | |
boolean | addAll(Collection<? extends E> c) 按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部。 | |
boolean | addAll(int index, Collection<? extends E> c) 从指定的位置开始,将指定 collection 中的所有元素插入到此列表中。 | |
void | clear() 移除此列表中的所有元素。 | |
Object | clone() 返回此 ArrayList 实例的浅表副本。 | |
boolean | contains(Object o) 如果此列表中包含指定的元素,则返回 true。 | |
void | ensureCapacity(int minCapacity) 如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。 | |
E | get(int index) 返回此列表中指定位置上的元素。 | |
int | indexOf(Object o) 返回此列表中首次出现的指定元素的索引,或如果此列表不包含元素,则返回 -1。 | |
boolean | isEmpty() 如果此列表中没有元素,则返回 true | |
int | lastIndexOf(Object o) 返回此列表中最后一次出现的指定元素的索引,或如果此列表不包含索引,则返回 -1。 | |
E | remove(int index) 移除此列表中指定位置上的元素。 | |
boolean | remove(Object o) 移除此列表中首次出现的指定元素(如果存在)。 | |
protected void | removeRange(int fromIndex, int toIndex) 移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之间的所有元素。 | |
E | set(int index, E element) 用指定的元素替代此列表中指定位置上的元素。 | |
int | size() 返回此列表中的元素数。 | |
Object[] | toArray() 按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组。 | |
| toArray(T[] a) 按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。 | |
void | trimToSize() 将此 ArrayList 实例的容量调整为列表的当前大小。 |