ArrayList源码分析
ArrayList底层由数组实现,并且可以动态扩容。
而数组在内存中分配的是一块连续的空间,直接根据数组的起始位置+偏移量就可以查询到,这使得查询效率高,所以ArrayList实现了 RandomAccess 接口来支持数据的随机访问。
除此之外,ArrayList还实现了 Cloneable 和 Serializable 接口,使其可以被克隆、序列化。
ArrayList 中的成员属性
1 private static final int DEFAULT_CAPACITY = 10; 2 3 private static final Object[] EMPTY_ELEMENTDATA = {}; 4 5 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 6 7 transient Object[] elementData; 8 9 private int size;
DEFAULT_CAPACITY 表示默认容量大小
EMPTY_ELEMENTDATA 表示空数组 --> 当构造函数中的参数为0时,elementData = EMPTY_ELEMENTDATA ==> new ArrayList(0);
DEFAULTCAPACITY_EMPTY_ELEMENTDATA 表示默认数组 --> 当使用无参构造器时,elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA ==> new ArrayList();
elementData 表示创建的Arraylist的底层数组,用来存放元素
size 表示数组中的元素个数,不等于数组长度(比如:默认的数组长度为10,但你可能只添加了四个元素,所以size为4)
ArrayList 中的构造器
1 public ArrayList(int initialCapacity) { 2 if (initialCapacity > 0) { 3 this.elementData = new Object[initialCapacity]; 4 } else if (initialCapacity == 0) { 5 this.elementData = EMPTY_ELEMENTDATA; 6 } else { 7 throw new IllegalArgumentException("Illegal Capacity: "+ 8 initialCapacity); 9 } 10 }
这个构造器传入一个整型参数,如果等于0,则使用 EMPTY_ELEMENTDATA 空数组
如果参数大于0,则创建一个相应 容量 的数组。
1 public ArrayList() { 2 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 3 }
无参构造器,使用默认容量的数组,容量大小为10
1 public ArrayList(Collection<? extends E> c) { 2 elementData = c.toArray(); 3 if ((size = elementData.length) != 0) { 4 if (elementData.getClass() != Object[].class) 5 elementData = Arrays.copyOf(elementData, size, Object[].class); 6 } else { 7 this.elementData = EMPTY_ELEMENTDATA; 8 } 9 }
有参构造器,参数是一个集合。
1.将参数转换为数组
2.判断该数组的长度,如果为0,则使用空数组;
否则,进一步判断该数组是否时 Object[] 类型,如果不是就将其转换为 Object[] 类型
数组扩容
1 private void grow(int minCapacity) { 2 int oldCapacity = elementData.length; 3 int newCapacity = oldCapacity + (oldCapacity >> 1); 4 if (newCapacity - minCapacity < 0) 5 newCapacity = minCapacity; 6 if (newCapacity - MAX_ARRAY_SIZE > 0) 7 newCapacity = hugeCapacity(minCapacity); 8 elementData = Arrays.copyOf(elementData, newCapacity); 9 }
假设我们使用 new ArrayList() 创建了一个容量为10的数组,然后往里面加入了10个元素,
此时,我们准备加入第11个元素,那么 elementData 数组就需要进行扩容了。
1.获取数组当前的容量
2.计算出数组当前容量扩容后的 假设容量(为当前容量的1.5倍)
3.如果假设容量不能满足扩容需求,则以需要的容量为准
添加元素
1 public boolean add(E e) { 2 ensureCapacityInternal(size + 1); 3 elementData[size++] = e; 4 return true; 5 }
在末尾添加一个元素
ensureCapacityInternal(size + 1); 检查是否需要扩容,需要扩容就进行扩容
1 public void add(int index, E element) { 2 rangeCheckForAdd(index); 3 ensureCapacityInternal(size + 1); 4 System.arraycopy(elementData, index, elementData, index + 1, 5 size - index); 6 elementData[index] = element; 7 size++; 8 }
1.检查要插入的位置是否下标越界
2.判断是否要扩容
3.利用System.arraycopy()方法将 index 下标之后的元素往后移一位
1 public boolean addAll(Collection<? extends E> c) { 2 Object[] a = c.toArray(); 3 int numNew = a.length; 4 ensureCapacityInternal(size + numNew); 5 System.arraycopy(a, 0, elementData, size, numNew); 6 size += numNew; 7 return numNew != 0; 8 }
在末尾添加一个集合
1.将集合 c 转换为数组
2.判断 (当前元素个数+要添加的元素个数) 是否需要扩容
3.利用 System.arraycopy() 方法将所有的元素添加到末尾
1 public boolean addAll(int index, Collection<? extends E> c) { 2 rangeCheckForAdd(index); 3 Object[] a = c.toArray(); 4 int numNew = a.length; 5 ensureCapacityInternal(size + numNew); 6 int numMoved = size - index; 7 if (numMoved > 0) 8 System.arraycopy(elementData, index, elementData, index + numNew, 9 numMoved); 10 System.arraycopy(a, 0, elementData, index, numNew); 11 size += numNew; 12 return numNew != 0; 13 }
在指定位置添加一个集合
1.检查要插入元素的下标是否越界
2.将集合 c 转换数组
3.计算出插入元素后需要往后移动一位的元素个数,如果个数为0,则说明实在末尾添加
4.要移动的元素个数大于0,利用 System.arraycopy() 方法将元素往后移动
5.利用 Syatem.arraycopy() 方法将集合 c 添加进去
1 private void ensureCapacityInternal(int minCapacity) { 2 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 3 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 4 } 5 ensureExplicitCapacity(minCapacity); 6 } 7 8 private void ensureExplicitCapacity(int minCapacity) { 9 modCount++; 10 if (minCapacity - elementData.length > 0) 11 grow(minCapacity); 12 }
移除元素
1 public E remove(int index) { 2 rangeCheck(index); 3 modCount++; 4 E oldValue = elementData(index); 5 int numMoved = size - index - 1; 6 if (numMoved > 0) 7 System.arraycopy(elementData, index+1, elementData, index, 8 numMoved); 9 elementData[--size] = null; 10 return oldValue; 11 }
1.检查要移除的元素下标是否越界
2.计算出移除元素后需要向前移动一位的元素的个数。
如果个数为0则说明移除的是最后一个元素
3.要移动的元素的个数大于0,则不是移除末尾元素,需要将移除元素 后面的所有元素往前移动一位 ==> 利用System.arraycopy()方法
4.将最后一个元素置为 null,方便 GC 回收,size减一
1 public boolean remove(Object o) { 2 if (o == null) { 3 for (int index = 0; index < size; index++) 4 if (elementData[index] == null) { 5 fastRemove(index); 6 return true; 7 } 8 } else {modCount != expectedModCount
1.判断数组中是否包含要移除的元素
2.如果包含,则调用 fastRemove(index) 方法将元素移除 ==> fastRemove(index) 与remove(index)方法相似,只是少了下标越界检查
在源码里面有一个 modCount 的成员
protected transient int modCount = 0;
每次添加或移除元素是,都会进行 modCount++
它主要是用在迭代器 Iterator 里面
1 public Iterator<E> iterator() { 2 return new Itr(); 3 }
它返回一个 Itr 类,是 ArrayList 中的一个内部类,里面有个成员 expectedModCount = modCount
1 final void checkForComodification() { 2 if (modCount != expectedModCount) 3 throw new ConcurrentModificationException(); 4 }
然后在上面得 checkForComodification() 方法中可以看到有一个 if 语句,如果 modCount != expectedModCount 则抛出 ConcurrentModificationException 异常
也就是,我们在使用迭代器遍历集合的时候,如果直接使用 list.add() 或 list.delete() 等修改元素个数得方法时,会抛出该异常