1、什么是ArrayList?
ArrayList实现了List接口,ArrayList用于有序地存储单个元素,元素允许重复它的底层实现是数组(插入复杂,查询简单)。它是非线程安全的。可以把它当作是一个可改变大小,并存储多种数据类型的数组,ArrayList的元素都存放在一个名为elementData的数组中。
2、ArrayList的模型
3.ArrayList的继承关系
public class ArrayList<E> extends AbstractList<E>mplements List<E>, RandomAccess, Cloneable, java.io.Serializable{...}
RandomAccess: 它是一个空接口,没有任何内容,仅作为一个标记接口,标识实现这个接口(在恒定时间内)可以支持快速随机访问(数组的特性);
Cloneable: 同样是一个空接口,用来指示Object类中的clone()方法可以用来合法的进行克隆;
Serializable: 实现Serializable接口是为了使类可持久化,此处不做探讨,详细可看这位大佬的文章:java类中serialVersionUID的作用;AbstractList: 对List接口的进一步补充,实现了一个List集合AUID(增删查改)的基本规范。**List接口:**List接口定义了一个List集合的基本规范,它的继承类对这些方法的实现基本都大同小异,再额外自己添加扩容规则,List接口主要有以下这些方法。
public interface List<E> extends Collection<E> {
//返回集合元素的数量
int size();
//返回集合是否为空
boolean isEmpty();
//查看是否包含某个元素
boolean contains(Object o);
//返回它的底层迭代器
Iterator<E> iterator();
//转成数组
Object[] toArray();
<T> T[] toArray(T[] a);
//在当前容器最后一个元素后面添加一个元素
boolean add(E e);
//从容器中删除某个元素
boolean remove(Object o);
//看是否包含c中的所有元素
boolean containsAll(Collection<?> c);
//将一个另外一个集合中的所有元素添加进去
boolean addAll(Collection<? extends E> c);
//将一个另外一个集合中的所有元素从某个位置添加进去
boolean addAll(int index, Collection<? extends E> c);
//删除某些元素
boolean removeAll(Collection<?> c);
//求集合的交集
boolean retainAll(Collection<?> c);
//替换某些元素
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
//传入一个比较器,对容器元素进行排序
@SuppressWarnings({"unchecked", "rawtypes"})
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
//清空容器
void clear();
//对象相等判断
boolean equals(Object o);
int hashCode();
//获取数组下标为index的元素
E get(int index);
//替换数组下标为index的元素
E set(int index, E element);
//在index处插入一个元素,后面的元素一次后移
void add(int index, E element);
//删除某处的元素,后面的元素依次前移
E remove(int index);
//返回容器中某元素第一次出现的数组下标,若无,返回-1
int indexOf(Object o);
//返回容器中某元素最后一次出现的数组下标,若无,返回-1
int lastIndexOf(Object o);
//返回容器的列表迭代器
ListIterator<E> listIterator();
//从index处开始,返回列表迭代器
ListIterator<E> listIterator(int index);
//返回容器的[fromIndex,toIndex)段
List<E> subList(int fromIndex, int toIndex);
//分割迭代器
@Overridedefault
Spliterator<E> spliterator() {
return Spliterators.spliterator(this,Spliterator.ORDERED);
}
}
大致的规范就是add()添加元素,remove()删除元素,set()替换元素,size()返回大小。
4.ArrayList的默认常变量
//序列化版本号,用于对象序列化时核对版本号,这里不做展开
private static final long serialVersionUID = 8683452581122892189L;
//默认容量为10
private static final int DEFAULT_CAPACITY = 10;
//初始空数组,final修饰,不可改变
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认容量的初始空数组,final修饰,不可改变
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放容器元素的数组
transient Object[] elementData;
//数组最大允许长度为:2147483647-8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//数组大小private int size;
看完上面的可能有些同学会奇怪,为什么要创建三个数组这么多,甚至其中有两个用了final关键字修饰?
这跟它的构造方法和添加操作有关,我们往下看就明白了。
5.ArrayList的构造方法
ArrayList有三个构造函数:
第一个,无参构造方法,将默认空数组传给存放数据的elementData数组。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
第二个,自定义初始容量initialCapacity传参,它分三种情况:
initialCapacity | elementData的值 |
---|---|
0 | EMPTY_ELEMENTDATA(空数组) |
>0 | Object[initialCapacity] |
<0 | 报错 |
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {//容量大于0
this.elementData = new Object[initialCapacity];//直接创建数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {//报错
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
第三个,传参另一个容器,复制构造一个ArrayList容器。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) { if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
从上面三个构造方法可以看出,当ArrayList以无参构造方法被创建时,它的初始容量为0;当它以自定义容量大小被创建时,它的初始容量为该自定义值。
6.ArrayList的add()方法
当容器被创建出来,就要往里面添加元素了。ArrayList有两个add()方法,根据传参不同,调用不同的方法。
//在最后添加一个元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e; //添加操作
return true;
}
//在指定位置插入一个元素
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
来看看add()方法是怎么实现的。
//先解释一下参数
//elementData:ArrayList存放元素的数组
//size:数组当前长度(元素的个数)
//minCapacity:minCapacity=size+1=添加一个新元素后的数组长度=添加元素后数组所需最小长度
//A:计算容量,如果当前数组为默认空数组
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//B:明确容量
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);//扩容
}
//C:明确内部容量
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
//D:添加操作
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
上面几个方法,方法名都长差不多,看着都头晕,所以我给他们标了ABCD方便识别。
calculateCapacity(A):看一下当前的element数组是不是默认的空数组,是的话,把默认容量10返回,不是的话,返回添加一个新元素后的数组长度(size+1);
ensureExplicitCapacity(B):如果添加新元素后的数组长度要比现在数组容量大,就调用扩容方法grow();
ensureCapacityInternal©:调用A和B,看看是不是得扩容数组;
add(D):调用C,看是否需要进行数组扩容,然后把元素放进数组里。
7.ArrayList的扩容机制
上面说到,ArrayList在进行add操作的时候会检查数组的长度,看是否需要扩容,ArrayList的扩容通过grow()方法实现,我们来看看这个方法。
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和数组最大允许长度中的一个
//扩容数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
注意里面一行代码:newCapacity = oldCapacity + (oldCapacity >> 1);,翻译一下就是新容量=原容量+0.5*原容量。
整部分代码的意思就是:你把你需要的数组长度传进来,我看看原来数组扩容1.5倍后多长,如果扩容后太长,超出限制,我就报错,数组只扩容一个位置来帮你存数据;如果扩容后不会太长,那我就直接1.5被扩容来放数据。
8.总结
对ArrayList的源码分析暂时就这么多了,还有一些东西没写出来,可能以后会补全吧。我还还只是个即将毕业的新人,文中有什么疏漏的地方,还望各位大佬不吝指教,在下方评论区指出。