ArrayList的源码解析

ArrayList

  1. 为什么空实例默认数组有的时候是EMPTY_ELEMENTDATA,而又有的时候是DEFAULTCAPACITY_EMPTY_ELEMENTDATA

  2. 为什么elementData要被transient修饰

本文章借鉴自微信公众号三太子敖丙

1.默认参数

// 序列化版本UID`
`private static final long`
    `serialVersionUID = 8683452581122892189L;`

`/**`
 `\* 默认的初始容量`
 `*/`

```java
`private static final int`
    `DEFAULT_CAPACITY = 10;`
```

`/**`
 `\* 用于空实例的共享空数组实例`
 `\* new ArrayList(0);`
 `*/`
`private static final Object[]`
    `EMPTY_ELEMENTDATA = {};`

`/**`
 `\* 用于提供默认大小的实例的共享空数组实例`
 `\* new ArrayList();`
 `*/`
`private static final Object[]`
    `DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};`

`/**`
 `\* 存储ArrayList元素的数组缓冲区`
 `\* ArrayList的容量,是数组的长度`
 `\*` 
 `\* non-private to simplify nested class access`
 `*/`
`transient Object[] elementData;`

`/**`
 `\* ArrayList中元素的数量`
 `*/`
`private int size;

类图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y2VPSSvz-1600653538610)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200920212828268.png)]

构造方法

带初始容量的构造方法

/**
 \* 带一个初始容量参数的构造方法
 *
 \* @param initialCapacity 初始容量
 \* @throws 如果初始容量非法就抛出
 \*     IllegalArgumentException
 */
public ArrayList(int initialCapacity) {
  if (initialCapacity > 0) {
    this.elementData =
        new Object[initialCapacity];
  } else if (initialCapacity == 0) {
    this.elementData = EMPTY_ELEMENTDATA;
  } else {
    throw new IllegalArgumentException(
        "Illegal Capacity: "+ initialCapacity);
  }
}


  • 如果initialCapacity < 0,就创建一个新的长度是initialCapacity的数组
  • 如果initialCapacity == 0,就使用EMPTY_ELEMENTDATA
  • 其他情况,initialCapacity不合法,抛出异常

无参构造方法

/**
 * 无参构造方法 将elementData 赋值为
 *   DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 */
public ArrayList() {
    this.elementData =
            DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

带一个集合参数的构造方法

/**
 * 带一个集合参数的构造方法
 *
 * @param c 集合,代表集合中的元素会被放到list中
 * @throws 如果集合为空,抛出NullPointerException
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    // 如果 size != 0
    if ((size = elementData.length) != 0) {
        // c.toArray 可能不正确的,不返回 Object[]
        // https://bugs.openjdk.java.net/browse/JDK-6260652
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(
                    elementData, size, Object[].class);
    } else {
        // size == 0
        // 将EMPTY_ELEMENTDATA 赋值给 elementData
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
  • 使用将集合转换为数组的方法
  • 为了防止c.toArray()方法不正确的执行,导致没有返回Object[],特殊做了处理
  • 如果数组大小等于0,则使用 EMPTY_ELEMENTDATA

那么问题来了,什么情况下c.toArray()会不返回Object[]呢?

public static void main(String[] args) {
    List<String> list = new ArrayList<>(Arrays.asList("list"));
    // class java.util.ArrayList
    System.out.println(list.getClass());

    Object[] listArray = list.toArray();
    // class [Ljava.lang.Object;
    System.out.println(listArray.getClass());
    listArray[0] = new Object();

    System.out.println();

    List<String> asList = Arrays.asList("asList");
    // class java.util.Arrays$ArrayList
    System.out.println(asList.getClass());

    Object[] asListArray = asList.toArray();
    // class [Ljava.lang.String;
    System.out.println(asListArray.getClass());
    // java.lang.ArrayStoreException
    asListArray[0] = new Object();
}

我们通过这个例子可以看出来,java.util.ArrayList.toArray()方法会返回Object[]没有问题。而java.util.Arrays的私有内部类ArrayList的toArray()方法可能不返回Object[]

为什么会这样?

我们看ArrayList的toArray()方法源码:

public Object[] toArray() {
    // ArrayLisy中 elementData是这样定义的
    // transient Object[] elementData;
    return Arrays.copyOf(elementData, size);
}

使用了Arrays.copyOf()方法:

public static <T> T[] copyOf(T[] original, int newLength) {
    // original.getClass() 是 class [Ljava.lang.Object
    return (T[]) copyOf(original, newLength, original.getClass());
}

copyOf()的具体实现:

public static <T,U> T[] copyOf(U[] original, 
          int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    /**
     * 如果newType是Object[] copy 数组 类型就是 Object 
     * 否则就是 newType 类型
     */
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

我们知道ArrayList中elementData就是Object[]类型,所以ArrayList的toArray()方法必然会返回Object[]

我们再看一下java.util.Arrays的内部ArrayList源码(截取的部分源码):

private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable {

    // 存储元素的数组
    private final E[] a;

    ArrayList(E[] array) {
        // 直接把接收的数组 赋值 给 a
        a = Objects.requireNonNull(array);
    }

    /**
     * obj 为空抛出异常
     * 不为空 返回 obj
     */
    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }

    @Override
    public Object[] toArray() {
        // 返回 a 的克隆对象
        return a.clone();
    }

}

这是Arrays.asList()方法源码

public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

不难看出来java.util.Arrays的内部ArrayList的toArray()方法,是构造方法接收什么类型的数组,就返回什么类型的数组。

所以,在我们上面的例子中,实际上返回的是String类型的数组,再将其中的元素赋值成Object类型的,自然报错。

插入方法

在列表最后添加指定元素

/**
 \* 在列表最后添加指定元素
 *
 \* @param e 要添加的指定元素
 \* @return true
 */
public boolean add(E e) {
  // 增加 modCount !!
  ensureCapacityInternal(size + 1); 
  elementData[size++] = e;
  return true;
}

modCount指定修改的次数

在指定位置添加指定元素

/**
 * 在指定位置添加指定元素
 * 如果指定位置已经有元素,就将该元素和随后的元素移动到右面一位
 *
 * @param index 待插入元素的下标
 * @param element 待插入的元素
 * @throws 可能抛出 IndexOutOfBoundsException
 */
public void add(int index, E element) {
    rangeCheckForAdd(index);


    // 增加 modCount !!
    ensureCapacityInternal(size + 1);
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

插入方法调用的其他私有方法

/**
 * 计算容量
 */
private static int calculateCapacity(
        Object[] elementData, int minCapacity) {

    if (elementData ==
            DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);//返回DEFAULT_CAPACITY, minCapacity中最大的一个
    }
    return minCapacity;
}

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(
            calculateCapacity(elementData, minCapacity)//判断是否需要扩容
    );
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)//最小容量大于你的现在容量需要进行扩容
        grow(minCapacity);//扩容
}

移除指定元素方法

/**
 \* 移除第一个在列表中出现的指定元素
 \* 如果存在,移除返回true
 \* 否则,返回false
 *
 \* @param o 指定元素
 */
public boolean 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;
}


扩容方法

扩容机制代码

/**
 \* 数组可以分配的最大size
 \* 一些虚拟机在数组中预留一些header words
 \* 如果尝试分配更大的size,可能导致OutOfMemoryError
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
 \* 增加容量,至少保证比minCapacity大
 \* @param minCapacity 期望的最小容量
 */
private void grow(int minCapacity) {
  // 有可能溢出的代码
  int oldCapacity = elementData.length;
  int newCapacity = oldCapacity + (oldCapacity >> 1);//右移  扩容为原来的1.5倍
  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);
}

/**
 \* 最大容量返回 Integer.MAX_VALUE
 */
private static int hugeCapacity(int minCapacity) {
  if (minCapacity < 0) // overflow
    throw new OutOfMemoryError();
  return (minCapacity > MAX_ARRAY_SIZE) ?
    Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}


  • 通常情况新容量是原来容量的1.5倍
  • 如果原容量的1.5倍比minCapacity小,那么就扩容到minCapacity
  • 特殊情况扩容到Integer.MAX_VALUE

​ 第一题答案:

​ 没有指定大小的情况下,new ArrayList()会将elementData 赋值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA。

​ 在指定大小的情况下new ArrayList(0)会将elementData 赋值为 EMPTY_ELEMENTDATA(空集合),EMPTY_ELEMENTDATA添加元素会扩容到容量为1,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA扩容之后容量为10。

测试代码:dfd/Test2.java

移除方法

移除指定下标元素方法remove()

/**`
 `\* 移除列表中指定下标位置的元素`
 `\* 将所有的后续元素,向左移动`
 `*`
 `\* @param 要移除的指定下标`
 `\* @return 返回被移除的元素`
 `\* @throws 下标越界会抛出IndexOutOfBoundsException`
 `*/`
`public E remove(int index) {`
  `rangeCheck(index);`

  `modCount++;`
  `E oldValue = elementData(index);`

  `int numMoved = size - index - 1;`
  `if (numMoved > 0)`
      `System.arraycopy(elementData,` 
          `index+1, elementData, index, numMoved);`
  `// 将引用置空,让GC回收`
  `elementData[--size] = null;`

  `return oldValue;`
`}

System.arraycopy(elementData,
index+1, elementData, index, numMoved);代码图解
在这里插入图片描述

私有移除方法

/*

 * 私有的 移除 方法 跳过边界检查且不返回移除的元素
   */
   private void fastRemove(int index) {
   modCount++;
   int numMoved = size - index - 1;
   if (numMoved > 0)
       System.arraycopy(elementData, index+1, elementData, index,
                        numMoved);
   // 将引用置空,让GC回收
   elementData[--size] = null;
   }

查找方法

查找指定元素的所在位置

/**
 * 返回指定元素第一次出现的下标
 * 如果不存在该元素,返回 -1
 * 如果 o ==null 会特殊处理
 */
public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}
/**
 \* 返回指定位置的元素
 *
 \* @param index 指定元素的位置 
 \* @throws index越界会抛出IndexOutOfBoundsException
 */
public E get(int index) {
  rangeCheck(index);

  return elementData(index);
}

 E elementData(int index) {
        return (E) elementData[index];
    }

 private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

根据index取下标对应的值

序列化方法

/**
 * 将ArrayLisy实例的状态保存到一个流里面
 */
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);

    // 按照顺序写入所有的元素
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

反序列化方法

/**
 * 根据一个流(参数)重新生成一个ArrayList
 */
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;

    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in capacity
    s.readInt();

    if (size > 0) {
        // be like clone(), allocate array based upon size not capacity
        ensureCapacityInternal(size);

        Object[] a = elementData;
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) { //将实际的数据遍历在进行实例化 
            a[i] = s.readObject();
        }
    }
}

看完序列化,反序列化方法,我们终于又能回答开篇的第二个问题了。elementData之所以用transient修饰,是因为JDK不想将整个elementData都序列化或者反序列化,而只是将size和实际存储的元素序列化或反序列化,从而节省空间和时间。

创建子数组

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

我们看一下简短版的SubList

private class SubList extends AbstractList<E> implements RandomAccess {
    private final AbstractList<E> parent;
    private final int parentOffset;
    private final int offset;
    int size;

    SubList(AbstractList<E> parent,
            int offset, int fromIndex, int toIndex) {
        this.parent = parent;
        this.parentOffset = fromIndex;
        this.offset = offset + fromIndex;
        this.size = toIndex - fromIndex;
        this.modCount = ArrayList.this.modCount;
    }

    public E set(int index, E e) {
        rangeCheck(index);
        checkForComodification();
        E oldValue = ArrayList.this.elementData(offset + index);
        ArrayList.this.elementData[offset + index] = e;
        return oldValue;
    }

    // 省略代码...
}
  • SubList的set()方法,是直接修改ArrayListelementData数组的,使用中应该注意
  • SubList是没有实现Serializable接口的,是不能序列化的

迭代器

创建迭代器方法

public Iterator<E> iterator() {
    return new Itr();
}

Itr属性

// 下一个要返回的元素的下标
int cursor;
// 最后一个要返回元素的下标 没有元素返回 -1
int lastRet = -1;
// 期望的 modCount
int expectedModCount = modCount;

Itr的hasNext() 方法

public boolean hasNext() {
    return cursor != size;
}

Itr的next()方法

public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

在迭代的时候,会校验modCount是否等于expectedModCount,不等于就会抛出著名的ConcurrentModificationException异常。什么时候会抛出ConcurrentModificationException

public static void main(String[] args) {
    ArrayList arrayList = new ArrayList();
    for (int i = 0; i < 10; i++) {
        arrayList.add(i);
    }
    remove(arrayList);
    System.out.println(arrayList);
}

public static void remove(ArrayList<Integer> list) {
    Iterator<Integer> iterator = list.iterator();
    while (iterator.hasNext()) {
        Integer number = iterator.next();
        if (number % 2 == 0) {
            // 抛出ConcurrentModificationException异常
            list.remove(number);
        }
    }
}

那怎么写才能不抛出ConcurrentModificationException?很简单,将list.remove(number);换成iterator.remove();即可。why?请看Itr的remove()源码…

Itr的remove()方法

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        // 移除之后将modCount 重新赋值给 expectedModCount
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

原因就是因为Itr的remove()方法,移除之后将modCount重新赋值给 expectedModCount。这就是源码,不管单线程还是多线程,只要违反了规则,就会抛异常。

t.remove(number);
}
}
}


> 那怎么写才能不抛出`ConcurrentModificationException`?很简单,将`list.remove(number);`换成`iterator.remove();`即可。why?请看Itr的`remove()`源码…

### Itr的remove()方法

```java
public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        // 移除之后将modCount 重新赋值给 expectedModCount
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

原因就是因为Itr的remove()方法,移除之后将modCount重新赋值给 expectedModCount。这就是源码,不管单线程还是多线程,只要违反了规则,就会抛异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值