概览

ArrayList类扩展AbstractList,实现了 List接口、RandomAccess 接口,因此支持随机访问。

  1. public class ArrayList<E> extends AbstractList<E>
  2. implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList类扩展AbstractList并执行List接口。
支持随机访问,但是在List的中间插入和移除元素时较慢。
底层为单向链表,基于数组实现

关键属性

  1. private static final int DEFAULT_CAPACITY = 10; // 默认初始大小
  2. private static final Object[] EMPTY_ELEMENTDATA = {}; // 空数组
  3. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // size为默认大小的空数组
  4. private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  5. transient Object[] elementData;
  6. private int size; //容器大小
  1. 定义在 AbstractList 中的。记录对 List 操作的次数。主要使用是在 Iterator,是防止在迭代的过程中集合被修改。
  2. protected transient int modCount = 0; // 用来记录AbstractList修改次数

构造器

  1. ArrayList(); // 建立一个默认大小的空数组,即=DEFAULTCAPACITY_EMPTY_ELEMENTDATA,但是在第一次添加元素的时候扩容到10
  2. ArrayList(Collection c); // 建立由c初始化的数组列表
  3. ArrayList(int capacity); // 建立指定初始容量的数组列表
  4. // 若指定的collection的大小为0或者capacity=0,建立一个空数组,即=EMPTY_ELEMENTDATA

1.添加元素

  • public boolean add(E e)

将元素填在原size后

  1. public boolean add(E e) {
  2. ensureCapacityInternal(size + 1); // Increments modCount!!
  3. elementData[size++] = e;
  4. return true;
  5. }
  • public void add(int index, E element)

将index位置上及之后的元素往后移一位,再将填充元素复制到index位置

  1. public void add(int index, E element) {
  2. rangeCheckForAdd(index);
  3. ensureCapacityInternal(size + 1); // Increments modCount!!
  4. System.arraycopy(elementData, index, elementData, index + 1,
  5. size - index);
  6. elementData[index] = element;
  7. size++;
  8. }
  • public boolean addAll(Collection<? extends E> c)

将填充元素复制到原size位置后

  1. public boolean addAll(Collection<? extends E> c) {
  2. Object[] a = c.toArray();
  3. int numNew = a.length;
  4. ensureCapacityInternal(size + numNew); // Increments modCount
  5. System.arraycopy(a, 0, elementData, size, numNew);
  6. size += numNew;
  7. return numNew != 0;
  8. }
  • public boolean addAll(int index, Collection<? extends E> c)

将index位置上及之后的元素往后移填充元素数目位,再将填充元素复制到index位置和其后面的填充元素数目位

  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); // Increments modCount
  6. // 将index后面的元素往后移
  7. int numMoved = size - index;
  8. if (numMoved > 0)
  9. System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
  10. System.arraycopy(a, 0, elementData, index, numNew);
  11. size += numNew;
  12. return numNew != 0;
  13. }

2.扩容

在添加元素前,都会去判断是否需要扩容

  1. 添加元素会调用ensureCapacityInternal(添加元素后数组内元素数=minCapacity)
  2. 计算最小容量:判断当前elementData是否=DEFAULTCAPACITY_EMPTY_ELEMENTDATA,即使用无参构造器创建,若等于,minCapacity<=10,即minCapacity=10,否则minCapacity=添加元素后数组内元素数
  3. 判断是否扩容
    1. 否:minCapacity <= elementData.length
    2. 是:minCapacity > elementData.length
  4. 扩容,计算新数组容量大小newCapacity, 默认是newCapacity=oldCapacity + (oldCapacity >> 1),但若有以下这几种情况
    1. minCapacity > elementData.length*1.5(一次性添加大量数据),newCapacity=minCapacity ;
    2. elementData.length*1.5要大于Integer.MAX_VALUE - 8
      1. elementData.length*1.5 > minCapacity > Integer.MAX_VALUE - 8 ,newCapacity = Integer.MAX_VALUE;
      2. elementData.length*1.5 > Integer.MAX_VALUE-8 > minCapacity ,newCapacity= Integer.MAX_VALUE - 8;
  5. 调用 Arrays.copyOf ,把原数组复制到新数组,属于浅拷贝。这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。

    1. // minCapacity = size+添加元素数目
    2. private void ensureCapacityInternal(int minCapacity) {
    3. ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    4. }
    5. // 为使用无参构造函数创建的ArrayList设置elementData.length的最小值,即默认值 10
    6. // elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA ?minCapacity=10:minCapacity = 添加元素后的size大小
    7. private static int calculateCapacity(Object[] elementData, int minCapacity) {
    8. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    9. return Math.max(DEFAULT_CAPACITY, minCapacity);
    10. }
    11. return minCapacity;
    12. }
    13. // 新size > elementData.length ,则将数组扩容
    14. private void ensureExplicitCapacity(int minCapacity) {
    15. modCount++;
    16. // overflow-conscious code
    17. if (minCapacity - elementData.length > 0)
    18. grow(minCapacity);
    19. }
    20. // 若 新size > elementData.length*1.5,elementData.length=新size值;
    21. // 若 elementData.length*1.5 > 新size > Integer.MAX_VALUE - 8 ,elementData.length = Integer.MAX_VALUE;
    22. // 若 elementData.length*1.5 > Integer.MAX_VALUE-8 > 新size ,elementData.length = Integer.MAX_VALUE - 8;
    23. // 以上不满足,elementData.length*=1.5
    24. //
    25. private void grow(int minCapacity) {
    26. // overflow-conscious code
    27. int oldCapacity = elementData.length;
    28. int newCapacity = oldCapacity + (oldCapacity >> 1);
    29. if (newCapacity - minCapacity < 0)
    30. newCapacity = minCapacity;
    31. if (newCapacity - MAX_ARRAY_SIZE > 0)
    32. newCapacity = hugeCapacity(minCapacity);
    33. // minCapacity is usually close to size, so this is a win:
    34. elementData = Arrays.copyOf(elementData, newCapacity);
    35. }
    36. private static int hugeCapacity(int minCapacity) {
    37. if (minCapacity < 0) // overflow
    38. throw new OutOfMemoryError();
    39. return (minCapacity > MAX_ARRAY_SIZE) ?
    40. Integer.MAX_VALUE :
    41. MAX_ARRAY_SIZE;
    42. }

3.删除元素

本质:

  • 调用 System.arraycopy() 方法拷贝数组,将需要删除的元素段之后的元素组复制到需要删除的起始位置,再将(size-删除数目) 到 size 的元素引用指向null。
  • 该操作的时间复杂度为 O(N),可以看出 ArrayList 删除元素的代价是非常高的。

方法:

  • public E remove(int index)

    • 检验index合法与否
    • 判断要删除的元素是否位于数组的最后一个位置
    • 若是最后一个,直接将数组最后一个元素置为null
    • 若不是,调用 System.arraycopy() 方法拷贝数组,将index位置之后的元素复制到index位置至size-1的位置,将数组最后一个元素置为null
    • 返回index位置上的元素

      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,numMoved);
      8. elementData[--size] = null; // clear to let GC do its work
      9. return oldValue;
      10. }
  • public boolean remove(Object o)

    • 判断o对象是否为空
    • 遍历查询获得要删除元素的下标index
    • E remove(int index) 与 private void fastRemove(int index) 基本相同。将index位置之后的元素复制到index位置至size-1的位置,将数组最后一个元素置为null
  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 {
  9. for (int index = 0; index < size; index++)
  10. if (o.equals(elementData[index])) {
  11. fastRemove(index);
  12. return true;
  13. }
  14. }
  15. return false;
  16. }
  17. private void fastRemove(int index) {
  18. modCount++;
  19. int numMoved = size - index - 1;
  20. if (numMoved > 0)
  21. System.arraycopy(elementData, index+1, elementData, index,numMoved);
  22. elementData[--size] = null; // clear to let GC do its work
  23. }
  • public boolean removeAll(Collection<?> c)

删除相同元素

  • 判断c是否为空,空抛出异常
  • 声明r,w,r是遍历数组的下标,w是存在原数组但不存在c的元素个数
  • 遍历数组,将存在原数组但不存在c的元素依次从0开始存放
  • 若是在调用contains,判断原数组元素是否存在c时抛出异常,将未遍历的部分拷贝至已整理部分的数组后
  • 若原数组与c没有相同元素,那么返回false,表示数组未修改
  • 若原数组存在与c相同的元素,那么返回true,并将下标为w至size的元素指向null
    1. public boolean removeAll(Collection<?> c) {
    2. Objects.requireNonNull(c);
    3. return batchRemove(c, false);
    4. }
    5. private boolean batchRemove(Collection<?> c, boolean complement) {
    6. final Object[] elementData = this.elementData;
    7. int r = 0, w = 0;
    8. boolean modified = false;
    9. try {
    10. for (; r < size; r++)
    11. if (c.contains(elementData[r]) == complement)
    12. elementData[w++] = elementData[r];
    13. } finally {
    14. // Preserve behavioral compatibility with AbstractCollection,
    15. // even if c.contains() throws.
    16. if (r != size) {
    17. System.arraycopy(elementData, r,
    18. elementData, w,
    19. size - r);
    20. w += size - r;
    21. }
    22. if (w != size) {
    23. // clear to let GC do its work
    24. for (int i = w; i < size; i++)
    25. elementData[i] = null;
    26. modCount += size - w;
    27. size = w;
    28. modified = true;
    29. }
    30. }
    31. return modified;
    32. }
  • public boolean retainAll(Collection<?> c)

删除c中不存在的那些元素,即保留相同元素

  • 判断c是否为空,空抛出异常
  • 声明r,w,r是遍历数组的下标,w是存在原数组且存在c的元素个数
  • 遍历数组,将存在原数组且存在c的元素依次从0开始存放
  • 若是在调用contains,判断原数组元素是否存在c时抛出异常,将未遍历的部分拷贝至已整理部分的数组后
  • 若c包含了所有原数组的元素,那么返回false,表示数组未修改
  • 若c没有包含了所有原数组的元素,那么返回true,并将下标为w至size的元素指向null
    • public void clear()

将数组内所有对象引用指向null

  1. public void clear() {
  2. modCount++;
  3. // clear to let GC do its work
  4. for (int i = 0; i < size; i++)
  5. elementData[i] = null;
  6. size = 0;
  7. }

4.查询元素

  • public E get(int index)

    1. public E get(int index) {
    2. rangeCheck(index);
    3. return elementData(index);
    4. }
    5. E elementData(int index) {
    6. return (E) elementData[index];
    7. }

5.迭代器

Iterator

单向遍历,从头至尾

  1. public Iterator<E> iterator() { return new Itr(); }
  2. private class Itr implements Iterator<E>{
  3. int cursor; // index of next element to return 下一个遍历的下标
  4. int lastRet = -1; // index of last element returned; -1 if no such 遍历最后一个的下标
  5. int expectedModCount = modCount;
  6. public boolean hasNext() {
  7. return cursor != size;
  8. }
  9. public E next() {
  10. checkForComodification();
  11. int i = cursor;
  12. if (i >= size)
  13. throw new NoSuchElementException();
  14. Object[] elementData = ArrayList.this.elementData;
  15. if (i >= elementData.length)
  16. throw new ConcurrentModificationException();
  17. cursor = i + 1;
  18. return (E) elementData[lastRet = i];
  19. }
  20. public void remove() {
  21. if (lastRet < 0)
  22. throw new IllegalStateException();
  23. checkForComodification();
  24. try {
  25. ArrayList.this.remove(lastRet);
  26. cursor = lastRet;
  27. lastRet = -1;
  28. expectedModCount = modCount;
  29. } catch (IndexOutOfBoundsException ex) {
  30. throw new ConcurrentModificationException();
  31. }
  32. }
  33. }

ListIterator

双向遍历
ListItr继承了Itr,包含了Itr所有方法,并扩展了hasPrevious、nextIndex、previousIndex 、previous、set和add方法

  1. public ListIterator<E> listIterator() { return new ListItr(0); }
  2. private class ListItr extends Itr implements ListIterator<E> {
  3. ListItr(int index) {
  4. super();
  5. cursor = index;
  6. }
  7. public boolean hasPrevious() {
  8. return cursor != 0;
  9. }
  10. public int nextIndex() {
  11. return cursor;
  12. }
  13. public int previousIndex() {
  14. return cursor - 1;
  15. }
  16. @SuppressWarnings("unchecked")
  17. public E previous() {
  18. checkForComodification();
  19. int i = cursor - 1;
  20. if (i < 0)
  21. throw new NoSuchElementException();
  22. Object[] elementData = ArrayList.this.elementData;
  23. if (i >= elementData.length)
  24. throw new ConcurrentModificationException();
  25. cursor = i;
  26. return (E) elementData[lastRet = i];
  27. }
  28. public void set(E e) {
  29. if (lastRet < 0)
  30. throw new IllegalStateException();
  31. checkForComodification();
  32. try {
  33. ArrayList.this.set(lastRet, e);
  34. } catch (IndexOutOfBoundsException ex) {
  35. throw new ConcurrentModificationException();
  36. }
  37. }
  38. public void add(E e) {
  39. checkForComodification();
  40. try {
  41. int i = cursor;
  42. ArrayList.this.add(i, e);
  43. cursor = i + 1;
  44. lastRet = -1;
  45. expectedModCount = modCount;
  46. } catch (IndexOutOfBoundsException ex) {
  47. throw new ConcurrentModificationException();
  48. }
  49. }
  50. }

6. Fail-Fast

modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。

在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。

  1. private void writeObject(java.io.ObjectOutputStream s)
  2. throws java.io.IOException{
  3. // Write out element count, and any hidden stuff
  4. //这里 记录操作前的 modCount
  5. int expectedModCount = modCount;
  6. s.defaultWriteObject();
  7. // Write out size as capacity for behavioural compatibility with clone()
  8. s.writeInt(size);
  9. // Write out all elements in the proper order.
  10. for (int i=0; i<size; i++) {
  11. s.writeObject(elementData[i]);//操作
  12. }
  13. //这里的modCount是操作后的 modCount与之前的作比较
  14. if (modCount != expectedModCount) {
  15. throw new ConcurrentModificationException();
  16. }
  17. }

7. 序列化

ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。

保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。

  1. transient Object[] elementData; // non-private to simplify nested class access

ArrayList 实现了 writeObject() 和 readObject() 来只序列化数组中有元素填充那部分内容

序列化时需要使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。

  1. private void writeObject(java.io.ObjectOutputStream s)
  2. throws java.io.IOException{
  3. // Write out element count, and any hidden stuff
  4. int expectedModCount = modCount;
  5. s.defaultWriteObject();
  6. // Write out size as capacity for behavioural compatibility with clone()
  7. s.writeInt(size);
  8. // Write out all elements in the proper order.
  9. for (int i=0; i<size; i++) {
  10. // 使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。
  11. s.writeObject(elementData[i]);
  12. }
  13. if (modCount != expectedModCount) {
  14. throw new ConcurrentModificationException();
  15. }
  16. }

反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理类似。

  1. private void readObject(java.io.ObjectInputStream s)
  2. throws java.io.IOException, ClassNotFoundException {
  3. elementData = EMPTY_ELEMENTDATA;
  4. // Read in size, and any hidden stuff
  5. s.defaultReadObject();
  6. // Read in capacity
  7. s.readInt(); // ignored
  8. if (size > 0) {
  9. // be like clone(), allocate array based upon size not capacity
  10. //根据size来分配内存,来控制只序列化数组中有元素填充那部分内容
  11. ensureCapacityInternal(size);
  12. Object[] a = elementData;
  13. // Read in all elements in the proper order.
  14. for (int i=0; i<size; i++) {
  15. // 使用的是 ObjectInputStream 的 readObject() 方法进行反序列化
  16. a[i] = s.readObject();
  17. }
  18. }
  19. }

8.System.arraycopy()和Arrays.copyOf()方法

Arrays.copyOf() 的源代码内部调用了 System.arraycopy() 方法。

  • System.arraycopy() 方法需要目标数组,将原数组拷贝到目标数组里,而且可以选择拷贝的起点和长度以及放入新数组中的位置
  • Arrays.copyOf() 是系统自动在内部创建一个数组,并返回这个新创建的数组。