java集合之ArrayList源码解析

编程

整体架构

ArrayList实际就是个数组结构,如图

index:数组下标
elementData:数组本身

其他基本概念:

/**

* Default initial capacity.

* 数组初始大小

*/

private static final int DEFAULT_CAPACITY = 10;

/**

* The size of the ArrayList (the number of elements it contains).

* 当前数组大小

* @serial

*/

private int size;

//表示当前数组被修改的版本次数

protected transient int modCount = 0;

类注释:
1、可以自动扩容
2、允许put null
3、size、set、put、add、get时间复杂度O(1)
4、非线程安全(作为共享变量时存在),必要时可以使用线程安全的SynchronizedList(性能低)

public boolean add(E e) {

synchronized (mutex) {// synchronized 是一种轻量锁,mutex 表示一个当前 SynchronizedList

return c.add(e);

}

}

5、增强for循环或迭代时,若数组大小改变,则抛出异常

源码解析

一、初始化:

//1、指定大小初始化

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);

}

}

//2、无参初始化 数组大小为空

//初始化时数组大小不是默认的10,第一次add时扩容为10

public ArrayList() {

this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

}

//3、指定初始数据初始化

public ArrayList(Collection<? extends E> c) {

elementData = c.toArray();

if ((size = elementData.length) != 0) {

// c.toArray might (incorrectly) not return Object[] (see 6260652)

if (elementData.getClass() != Object[].class)

elementData = Arrays.copyOf(elementData, size, Object[].class);

} else {

// replace with empty array.

this.elementData = EMPTY_ELEMENTDATA;

}

}

二、新增、扩容、删除

新增:

public boolean add(E e) {

//确保数组大小是否足够,不够执行扩容,size 为当前数组的大小

ensureCapacityInternal(size + 1); // Increments modCount!!

//直接赋值,线程不安全

elementData[size++] = e; 对null无校验,因此可以存入null值

return true;

}

扩容:

private void ensureCapacityInternal(int minCapacity) {

//如果初始化数组大小时,有给定初始值,以给定的大小为准,不走 if 逻辑

if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

}

//确保容积足够

ensureExplicitCapacity(minCapacity);

}

private void ensureExplicitCapacity(int minCapacity) {

//记录数组被修改

modCount++;

// 如果我们期望的最小容量大于目前数组的长度,那么就扩容

if (minCapacity - elementData.length > 0)

grow(minCapacity);

}

//扩容,并把现有数据拷贝到新的数组里面去

private void grow(int minCapacity) {

int oldCapacity = elementData.length;

// oldCapacity >> 1 是把 oldCapacity 除以 2 的意思 这里注意:ArrayList扩容是1+0.5倍

int newCapacity = oldCapacity + (oldCapacity >> 1);

// 如果扩容后的值 < 我们的期望值,扩容后的值就等于我们的期望值

if (newCapacity - minCapacity < 0)

newCapacity = minCapacity;

// 如果扩容后的值 > jvm 所能分配的数组的最大值,那么就用 Integer 的最大值

if (newCapacity - MAX_ARRAY_SIZE > 0)

newCapacity = hugeCapacity(minCapacity);

扩容时源码中对上下数组大小做了校验,有很清晰的数组大小溢出意识,平时开发时应借鉴

// 通过复制进行扩容

elementData = Arrays.copyOf(elementData, newCapacity);

}

扩容动态图:

删除:

public boolean remove(Object o) {

  // 如果要删除的值是 null,找到第一个值是 null 的删除

  if (o == null) {

    for (int index = 0; index < size; index++)

      if (elementData[index] == null) { 可以删除null值

        fastRemove(index);

        return true;

      }

  } else {

    // 如果要删除的值不为 null,找到第一个和要删除的值相等的删除

    for (int index = 0; index < size; index++)

      // 这里是根据  equals 来判断值相等的,相等后再根据索引位置进行删除

      if (o.equals(elementData[index])) { //找到需要删除的索引

        fastRemove(index);

        return true;

      }

  }

  return false;

}

//根据索引位置进行元素删除

private void fastRemove(int index) {

  // 记录数组的结构要发生变动了

  modCount++;

  // numMoved 表示删除 index 位置的元素后,需要从 index 后移动多少个元素到前面去

  // 减 1 的原因,是因为 size 从 1 开始算起,index 从 0开始算起

  int numMoved = size - index - 1;

  if (numMoved > 0)

    // 从 index +1 位置开始被拷贝,拷贝的起始位置是 index,长度是 numMoved

    System.arraycopy(elementData, index+1, elementData, index, numMoved);

  //数组最后一个位置赋值 null,帮助 GC

  elementData[--size] = null;

}

三、迭代器

implement java.util.Iterator

迭代器的一些参数:
int cursor;// 迭代过程中,下一个元素的位置,默认从 0 开始。
int lastRet = -1; // 新增场景:表示上一次迭代过程中,索引的位置;删除场景:为 -1。
int expectedModCount = modCount;// expectedModCount 表示迭代过程中,期望的版本号;modCount 表示数组实际的版本号。

迭代器源码分析:

//是否存在下一个可迭代的值

public boolean hasNext() {

return cursor != size;//cursor 表示下一个元素的位置,size 表示实际大小,如果两者相等,说明已经没有元素可以迭代了,如果不等,说明还可以迭代

}

//下一个可迭代的值

public E next() {

//迭代过程中,判断版本号有无被修改,有被修改,抛 ConcurrentModificationException 异常

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();

}

public void remove() {

// 如果上一次操作时,数组的位置已经小于 0 了,说明数组已经被删除完了

if (lastRet < 0)

throw new IllegalStateException();

//迭代过程中,判断版本号有无被修改,有被修改,抛 ConcurrentModificationException 异常

checkForComodification();

try {

ArrayList.this.remove(lastRet);

cursor = lastRet;

// -1 表示元素已经被删除,这里也防止重复删除

lastRet = -1;

// 删除元素时 modCount 的值已经发生变化,在此赋值给 expectedModCount

// 这样下次迭代时,两者的值是一致的了

expectedModCount = modCount;

} catch (IndexOutOfBoundsException ex) {

throw new ConcurrentModificationException();

}

}

以上是 java集合之ArrayList源码解析 的全部内容, 来源链接: utcz.com/z/510427.html

回到顶部