说到 ArrayList 通常都会有一道面试题,ArrayList 和 LinkedList 有什么区别?一般都会说,ArrayList 的底层是数组实现,LinkedList 底层是链表实现,故 ArrayList 的查找快,LinkedList 的插入快,那么这么说是正确的么?还是片面了?

先看 ArrayList 的实现

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

我们可以看到 ArrayList 集成了 RandomAccess, Cloneable, java.io.Serializable 三个接口,我们先说这三个接口的作用

RandomAccess

RandomAccess 只是一个标志接口,没有任何内容,实现这个这个接口的 List 集合是支持快速随机访问(下标访问)

  1. public interface RandomAccess {
  2. }

也就是说,实现了这个接口的集合是支持 快速随机访问 策略的。同时,官网还特意说明了,如果是实现了 RandomAccess 接口的 List,那么使用 for 循环的方式获取数据会优于用迭代器获取数据

  1. if(object instanceof RandomAccess) {
  2. // 使用 for 循环遍历
  3. }

Cloneable

拷贝的都是栈中的对象

Cloneable 是一个标记接口,实现了这个接口,就可以实现克隆

  1. public interface Cloneable {
  2. }

浅拷贝

基本类型是独立的,不受干扰引用类型是不是独立的,会受干扰,String 类型除外

String 类型虽然是引用类型,但是 String 类型是 final 修饰的,不可被修改,所以 String 类型也不会受干扰

Serializable

Serializable 是一个标记接口,实现了这个接口的类,就可以实现序列化,他需要一个版本号 serialVersionUID 属性,private static final long serialVersionUID = -3790927052270933690L; 这个属性就是类的指纹信息,就可以实现序列化和反序列化(存盘、网络传输)

  1. public interface Serializable {
  2. }

ArrayList 的几个属性

  1. /**
  2. * 默认的容量
  3. */
  4. private static final int DEFAULT_CAPACITY = 10;
  5. /**
  6. * 当指定了容量,且没有添加元素时,就会使用 EMPTY_ELEMENTDATA 来指向 ArrayList
  7. * 表示 ArrayList 是一个空集合
  8. */
  9. private static final Object[] EMPTY_ELEMENTDATA = {};
  10. /**
  11. * 当没有指定容量,且没有添加元素时,就会使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 来指向 ArrayList
  12. * 表示 ArrayList 是一个空集合
  13. */
  14. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  15. /**
  16. * 真正存储数据的数组
  17. */
  18. transient Object[] elementData;
  19. /**
  20. * 数组的长度
  21. */
  22. private int size;

那么 EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA 都表示一个空集合,那么他们有什么区别呢?为什么这么设计呢?其实这样做是为了判断加载因子,为扩容做准备

由于空集合指向的是 private static final Object[] EMPTY_ELEMENTDATA = {} ,且这个数组是 static 修饰的,所以无论我们创建多少个空集合 new ArrayList<>();
只会指向同一个空数组对象,也就是 EMPTY_ELEMENTDATA 这样做的原因是为了节约空间**

关于扩容

modCount:记录操作数,是做 fail-fast 用的minCapacity = 当前集合的容量 + 1

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

当现有的集合元素为数组的长度时候,就会触发扩容机制

  1. private void ensureExplicitCapacity(int minCapacity) {
  2. modCount++;
  3. // overflow-conscious code
  4. if (minCapacity - elementData.length > 0)
  5. grow(minCapacity);
  6. }

默认先计算:新的容量 = 老的容量 + 老的容量的一半

  • 新的容量 - 要扩容的容量 < 0,就是用要扩容的容量
  • 新的容量 - Integer.MAX_VALUE - 8 > 0,那么可能就超出最大范围了,会抛出 OOM
  • 否则新的容量就等于 Integer.MAX_VALUE
    1. private void grow(int minCapacity) {
    2. // overflow-conscious code
    3. int oldCapacity = elementData.length;
    4. int newCapacity = oldCapacity + (oldCapacity >> 1);
    5. if (newCapacity - minCapacity < 0)
    6. newCapacity = minCapacity;
    7. if (newCapacity - MAX_ARRAY_SIZE > 0)
    8. newCapacity = hugeCapacity(minCapacity);
    9. // minCapacity is usually close to size, so this is a win:
    10. elementData = Arrays.copyOf(elementData, newCapacity);
    11. }
    最后其实就是复制数组操作,完成扩容

指定下标添加元素

如果指定下标添加元素,在判断完扩容的后,会直接拷贝从要添加的元素位置到最后一个元素,然后将当前的 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. }

删除元素

计算出要从当前 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,
  8. numMoved);
  9. elementData[--size] = null; // clear to let GC do its work
  10. return oldValue;
  11. }

迭代器

使用迭代器删除元素不会报错的原因是因为 modCout 会更新

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

小知识点

如果可以计算出容量,可以指定容量大小,减少扩容次数