ArrayList源码分析--基于JDK1.8

ArrayList

位置: java.util.ArrayList

public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable
ArrayList源码分析--基于JDK1.8_第1张图片

ArrayList是什么?

  1. ArrayList可以说是一个可变长度的数组,底层基于变长数组实现,所以ArrayList的查询速度是很快的
  2. ArrayList允许空值和重复元素,当往ArrayList中添加的元素数量大于其底层数组的容量时,就会通过括容机制重新生成一个更大的数组
  3. ArrayList是线程不安全,在并发环境下,多个线程同时操作ArrayList,会引发不可预知的错误或异常

ArrayList的成员变量

/**
 * 序列化
 */
private static final long serialVersionUID = 8683452581122892189L;

/**
 * 默认初始容量。 当add第一个元素的时候初始化长度 10
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * 用于空实例的共享空数组实例。
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * 用于默认大小的空实例的共享空数组实例。我们
 * 将其与空的元素数据区分开来,以了解何时
 * 添加第一个元素。 
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * 存储ArrayList元素的数组缓冲区。
 * ArrayList的容量是此数组缓冲区的长度。
 * 任何 elementData==DEFAULTCAPACITY的空ArrayList   
 * elementData 将在添加第一个元素时扩展到默认容量。
 * 这个数组时真正存储ArrayList集合的元素的数组
 */
transient Object[] elementData; // 非私有以简化嵌套类访问

/**
 * ArrayList的大小(它包含的元素数)。
 * @serial
 */
private int size;
/**
 * 要分配的数组的最大大小。
 * Integer.MAX_VALUE = 2147483647
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

ArrayList的构造函数

ArrayList的构造函数有三个
无参构造↓↓↓↓↓↓
构造方法中elementData初始化为空的DEFAULTCAPACITY_EMPTY_ELEMENTDATA,当调用add方法添加第一个元素的时候,才会进行扩容,扩容大小为private static final int DEFAULT_CAPACITY = 10;

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

指定初始容量的构造↓↓↓↓↓↓
这个构造方法是让我们创建ArrayList的时候就指定容量大小initialCapacity
此构造函数中判断了三种情况

  • initialCapacity > 0 参数大于0 ,elementData初始化为initialCapacity大小的数组
  • initialCapacity == 0 参数等于0,elementData初始化为空数组,即private static final Object[] EMPTY_ELEMENTDATA = {};
  • initialCapacity < 0 参数小于0,抛出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);
        }
    }

指定初始集合的元素的构造↓↓↓↓↓↓
此构造方法的参数是一个Collection集合,将一个Collection集合转为ArrayList集合,如果传入的Collection为null就会报空指针异常

    public ArrayList(Collection c) {
        elementData = c.toArray();
        // 如果 elementData 不等于 0
        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;
        }
    }

以上是ArrayList的成员变量和构造方法,我们使用时可以使用默认的空参构造,也可以指定使用指定长度的构造,这两种我在工作的时候使用较多,第三种构造没怎么使用过,具体的使用看业务需求而定吧!

下面看一下ArrayList的一些操作方法

ArrayList的add方法

ArrayList的add方法是将元素添加到arrayList的末尾。看一下他是如何实现的!

下面的代码是有关add相关的所有代码

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

    ensureCapacityInternal方法

    调用 calculateCapacity 方法判断是不是空数组,如果使用空参构造方法创建,并且第一次添加的时候,那么 minCapacity = size(0) + 1 = 1, 然后比较 Math.max(DEFAULT_CAPACITY, minCapacity);比较,DEFAULT_CAPACITY = 10,所以DEFAULT_CAPACITY大,所以第一次添加的时候就初始化默认容量为10。在 调用ensureExplicitCapacity方法判断是否需要进行扩容

    ensureExplicitCapacity方法

    这个方法判断是否需要扩容,如果inCapacity - elementData.length > 0成立,说明将添加的元素索引已经大于数组的长度了,需要进行扩容,其中grow()方法就是执行扩容操作

    grow方法

  • oldCapacity为旧数组的容量
  • newCapacity为新数组的容量,新数组的容量的大小=oldCapacity + (oldCapacity >> 1); 即新容量为旧容量的1.5倍
  • 判断新数组的容量是否小于最小需求容量,如果小于那就将最小数组容量作为数组的新容量
  • 判断新容量是否大于MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;,如果大于,则通过hugeCapacity方法比较 (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE:MAX_ARRAY_SIZE;
  • 扩容并拷贝原数组中的元素 Arrays.copyOf(原数组, 新数组容量);
  1. elementData[size++] = e; 把传入的参数放到数组中
  2. return true; 返回值
/**
 * 将指定的元素追加到列表的末尾。
 *
 * @param e 元素,以附加到此列表中
 * @return true (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

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

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 * 增加容量,以确保它能容纳至少 由最小容量参数指定的元素数量。
 * @param minCapacity the desired minimum capacity 期望的最小容量
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    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: minCapacity通常接近大小,所以这是一个胜利:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

ArrayList的add(index, Element);

这个方法实在指定索引(index)位置添加元素(E)

  1. 首先调用rangeCheckForAdd方法判断指定的索引是否越界,越界就报异常
  2. 没有越界,调用ensureCapacityInternal方法判断是否需要扩容
  3. 使用System.arraycopy方法将index及其后面的元素都往后一一位
  4. 将新元素(E)插入index位置
/**
 * 在列表的指定位置插入指定的元素。
 *  将元素当前的位置(如果有的话)和后续的元素向右移动(在它们的索引中添加一个)。
 *
 * @param index 要插入指定元素的索引
 * @param 要插入的元素
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public void add(int index, E element) {
    rangeCheckForAdd(index); // 检查 下表是否越界
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}
// 检查给定的索引是否在范围内,不在就抛出异常
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private String outOfBoundsMsg(int index) {
    return "Index: "+index+", Size: "+size;
}
System.arraycopy(src, srcPos, dest, destPos, length)

作用:将指定源数组中的数组从指定位置复制到目标数组的指定位置。
参数:

  • src 原数组,
  • srcPos:原数组中的起始位置,
  • dest: 目标数组,
  • destPos: 目标数组中的起始位置
  • length:要复制的原数组元素的数量

ArrayList的addAll(Collection c);

这个方法是将指定集合c,中的所有元素追加到末尾

/**
 * 将指定集合中的所有元素追加到列表的末尾
 *   按照指定集合的迭代器返回的顺序排列。
 *   如果在操作进行时修改了指定的集合,则此操作的行为未定义。
 *    (这意味着,如果指定的集合是这个列表,并且这个列表是非空的,则此调用的行为是未定义的。)
 *
 * @param c 集合,其中包含要添加到此列表的元素
 * @return true 如果该列表因调用而改变
 * @throws NullPointerException 如果指定的集合为空
 */
public boolean addAll(Collection c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

ArrayList的addAll(int index, Collection c)

作用:从指定位置index开始,将指定集合C中的所有元素追加到列表中
从源码中可以看到,如果 size - index > 0 ,就是先进行赋值,
最后c中的所有元素再将指定index以及后面对应长度的值覆盖

public boolean addAll(int index, Collection c) {
    rangeCheckForAdd(index);

    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount

    int numMoved = size - index;
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew, numMoved);

    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

ArrayList的get(int index)方法

在get方法中,通过index获取元素

  1. 首先调用rangeCheck(index)方法判断index是否合法,不合法就包IndexOutOfBoundsException异常。
  2. 合法,就调用elementData(index)方法返回index处的元素
/**
 * 返回列表中指定位置的元素。
 *
 * @param  index 要返回的元素的索引
 * @return 此列表中指定位置的元素
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

rangeCheck方法

/**
 * 检查给定索引是否在范围内。
 如果不是,则抛出适当的运行时异常。
 这个方法不* *检查索引是否为负:
 它总是在数组访问之前使用,
 如果索引为负则抛出ArrayIndexOutOfBoundsException。
 */
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

elementData方法

直接通过index获取数组元素,时间复杂度为O(1)。

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

ArrayList的remove方法

E remove(int index)

这个方法按照索引删除。

  1. 调用rangeCheck(index)方法判断索引index是否合法,不合法,抛异常
  2. 合法,根据index获取需要删除的元素,这里获取,最后返回(return)。
  3. size - index -1; 删除了数组中的元素,后面的元素往需要往前移动。
  4. 判断 size - index -1 是否大于0,大于需要往前移动,等于0,说明是最后一个元素,不需要移动。
  5. 将原数组的最后一个元素赋值为null。清除,让GC完成它的工作
/**
 * 移除此列表中指定位置的元素。
 * 将后面的元素向左移动(从它们的索引中减去1)
 *
 * @param index 要删除的元素的索引
 * @return 从列表中删除的元素
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E remove(int index) {
    rangeCheck(index);

    modCount++; // 记录了ArrayList结构性变化的次数。
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

boolean remove(Object o)

根据元素删除,会删除和参数匹配的第一个元素。

  1. 如果参数为null,则遍元素中的第一个为null的元素的index,然后调用fastRemove(index) 根据索引删除,
  2. 如果不为null,遍历元素中第一个参数匹配的元素索引,然后调用fastRemove(index) 根据索引删除。
/**
 * 从列表中删除指定元素的第一个出现项,
 * 如果它存在的话。如果列表中不包含该元素,则不会对其进行更改
 *  .  更正式地说,删除索引最低的元素
 * i such that
 * (o==null ? get(i)==null : o.equals(get(i)))
 * (如果存在这样的元素)  Returns true if this list
 * 如果这个列表包含指定的元素(或者同样地,如果这个列表由于调用而改变)。
 *
 * @param o 要从列表中删除的元素(如果存在)
 * @return true 如果此列表包含指定元素
 */
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;
}
fastRemove(index)方法

此方法根据索引删除所引处的元素。原理与remove(int index)方法相同,但是没有返回值

/*
 * 跳过边界检查且不返回已删除值的私有移除方法。
 */
private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

ArrayList的Set(int index, E element)方法

此方法根据index修改index处的值

  1. 首先判断index是否合法,不合法抛异常
  2. 合法,则根据index查询元素,最后把index处的元素替换为新元素,return 旧元素
/**
 * 将列表中指定位置的元素替换为指定元素。
 *
 * @param index 要替换的元素的索引
 * @param element 元素存储在指定位置
 * @return 先前位于指定位置的元素
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

ArrayList的size()方法

此方法直接返回size变量。上述的add的所有方法都会size++; addAll方法会使size+参数的元素长度,上述的删除元素的时候,会--size。


private int size; // ArrayList的成员变量

public int size() {
    return size;
}

文章不定时更新

ArrayList源码分析--基于JDK1.8_第2张图片

你可能感兴趣的:(java)