总结:
-
ArrayList底层是基于数组
-
无参数是实例的时候容器大小是为0,添加元素的时候容器大小最小为10
-
add方法是先检查是否扩容,然后加入到数组最后
-
add(index ,e)方法是先检查index的值是否符合要求,然后再扩容,接着把index到最后的所有元素都移到自己后一位的位置,最后再把需要加入的值加入到index位置中
-
扩容机制为首先扩容为原始容量的 1.5 倍。如果1.5倍太小的话,则将我们所需的容量大小赋值给 newCapacity,如果1.5倍太大或者我们需要的容量太大,那就直接拿 newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE 来扩容。扩容之后是通过数组的拷贝来确保元素的准确性的,所以尽可能减少扩容操作。 ArrayList 的最大存储能力:Integer.MAX_VALUE。
-
如果需要边遍历边删除,则需要使用迭代器,并且使用迭代器的remove方法
1. 继承
ArrayList继承AbstractList,List,RandomAccess,Cloneable,Serializable,可能有人会奇怪了,AbstractList已经继承了List,为什么ArrayList又继承List。这个设计很巧妙,不仅仅是ArrayList,HashMap,LinkedList等等都是这样子的,那为什么要这样设计呢?其实这是为了反射。我们来举个例子:
interface ITest {
void test();
}
public class ParentClass implements ITest{
@Override
public void test() {
System.out.println("parentClass test");
Class[] classes=this.getClass().getInterfaces();
System.out.println(classes);
}
}
public class SubClass extends ParentClass {
@Override
public void test() {
System.out.println("SubClass` interfaces:");
Class[] classes=this.getClass().getInterfaces();
System.out.println(Arrays.toString(classes));
}
}
public class InheritClass extends ParentClass implements ITest {
@Override
public void test() {
System.out.println("InheritClass interfaces");
Class[] classes=this.getClass().getInterfaces();
System.out.println(Arrays.toString(classes));
}
}
public static void main(String[] strings){
SubClass subClass=new SubClass();
InheritClass interitClass=new InheritClass();
subClass.test();
interitClass.test();
}
执行结果:
SubClass` interfaces:
[]
InheritClass interfaces:
[interface javat.inherit.ITest]
这里SubClass与InheritClass的区别就是InheritClass多继承了ITest的接口
2. 构造函数
ArrayList主要是3个构造方法,其实都是为了初始化容器,我们就来看看最简单的方法吧:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
其实就是设定容器,一个大小为0的空容器,这里其实还有个常量,跟DEFAULTCAPACITY_EMPTY_ELEMENTDATA,这两个常量有什么区别呢:简单来讲就是第一次添加元素时知道该 elementData 从空的构造函数还是有参构造函数被初始化的。以便确认如何扩容。
3. 主要方法
1. ensureCapacityInternal(int minCapacity):
这个方法主要是设定容器的上下线【下限是10】,以及扩容的step【当前容量的1.5倍】
//就直接调用这个
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
//设定当前的容量,最小值为10
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//为空的时候
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//DEFAULT_CAPACITY=10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return 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;
//当前容量增大1.5倍
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);
}
2.add(E e):
往其添加元素:
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
就先看需不需要扩容,然后直接把需要加入的元素放入到数组中。
3.add(int index, E element):
往特定的位置加入元素:
//确定当前插入的位置是否在容器的范围内
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
这里的过程其实就是先扩容,然后把index及其后面的所有元素一一对应赋值给其后面一个位置,最后再将要放入的元素放入到index位置中,System.arraycopy() 方法将旧数组元素拷贝至一个新的数组中去
4.remove(Object o):
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
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
}
这个方法看上去也比较简单,主要做的就是找到该元素所处的位置,然后根据这个位置index及其后面的元素相当于往前面移动一个单元(其实是复制)然后再把最后一个元素置为null,这里提一下,remove(int index)跟fastRemove(int index)方法非常相似,remove(int index)跟remove(Object o)返回值是不同的
get和set方法就不说了,源码也非常简单,其实就是对数组的操作。
4.注意
有使用过集合的都知道,在用遍历集合的时候是不可以对集合进行 remove操作的,因为 remove 操作会改变集合的大小。从而容易造成结果不准确甚至数组下标越界,更严重者还会抛出 ConcurrentModificationException。我们来写个例子:
private static void test4(){
ArrayList<Integer> testList=new ArrayList<>();
testList.add(1);
testList.add(2);
testList.add(3);
testList.add(4);
Iterator<Integer> iterator=testList.iterator();
while(iterator.hasNext()){
Integer i=iterator.next();
if(i==2)----2
testList.remove(i);---1
}
}
结果:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
我们先找到出现这个错误的地方在哪,在Itr,即ArrayList的iterator中的next方法中有个checkForComodification方法,该方法会触发该异常,从源码中可以看到,在Itr初始化的时候expectedModCount = modCount,modCount是ArrayList中的,在ArrayList的remove方法会改变modCount的值,上面也说了modCount是记录操作ArrayList的次数,为了避免上面出现的这种异常,需要吧1处改成iterator.remove()就行了
说到这里,其实这里还有个问题,如果把上面的实例代码2处修改成i==3,该程序将会正常的运行,可以试一试,如果按照这样的操作,只要是删除倒数第二个,都能正常运行。一开始我还是以为是expectedModCount是被修改了,但仔细看过源码,并不是自己想像的这样,expectedModCount还是未被修改,但通过断点调试,发现最后一次会卡在iterator.hasNext()
public boolean hasNext() {
return cursor != size;
}
我们知道next方法会使得cursor+1,然后ArrayList的remove方法,size会-1,这样删除了倒数第二个后,会使得cursor==size,也就不会执行next方法了,从而造成了这种错觉。