ArrayList源码阅读(上)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/starexplode/article/details/80469079

ArrayList源码阅读

ArrayList是使用比较多的一个List,它的底层实现是使用的是一个数组,从继承性来讲,它继承了AbstractList的接口,并实现了List、RandomAccess、Cloneable和Serializable序列化接口。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
}

下面的是ArrayList的继承结构图:
ArrayList继承图

ArrayList中的一些变量

首先来看一下ArrayList中的一些变量:

// 序列化ID
private static final long serialVersionUID = 8683452581122892189L;
// ArryList数组的默认的容量 
private static final int DEFAULT_CAPACITY = 10; 
// 一个空的数组,当用户指定容量为0时,返回该数组 
private static final Object[] EMPTY_ELEMENTDATA = {};
// 一个空数组的实例
// - 当用户没有指定ArrayList的容量时,也就是调用无参构造的时候,返回的是该数组 刚创建的ArrayList,其中的数据量为0
// - 当用户第一次添加元素时,该数组将会扩容,变为默认的10(DEFAULT_CAPACITY) 通过ensureCapacityInternal()
// 它于EMPTY_ELEMENTDATA的区别是,该数组是默认返回的,EMPTY_ELEMENTDATA是用户指定容量为0时返回的
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// ArrayList基于数组实现,该数组保存数据,ArrayList的容量就是该数组的长度 
// - 该值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
// - 当用户第一次添加元素进入ArrayList中时,该数组将扩容为DEFAULT_CAPACITY
// - transient 修饰element表示不序列化这个变量
transient Object[] elementData;
// ArrayList中元素的个数
private int size;

/**
 * 数组缓冲区最大容量
 * The maximum size of array to allocate.
 * - 一些VM会在一个数组中存储某些介质 为什么减8的原因
 * - 尝试分配这个最大存储容量,可能会导致OutOfMemoryError(当该值 > VM 限制时)
 * Some VMs reserve some header words in an array.
 * Attempts to allocate larger arrays may result in
 * OutOfMemoryError: Requested array size exceeds VM limit
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

还有一个很重要的变量,它不在ArrayList中,它在AbstractList中,它就是modCount

protected transient int modCount = 0;

这个变量同样不参与序列化,这个变量的作用是记录ArrayList的修改次数,记录它的修改次数有什么用呢?这个会在后面说明。

ArrayList的构造器

上面也说道的一些变量它们与ArrayList的构造器相关,看了这块就会明白上面的话了。
ArrayList有三个构造器,分别是指定容量大小的,无参的和一个参数为Collection的构造器。
指定大小的构造器:

/**
 * 创建一个初始容量的,空的ArrayList
 * @param initialCapacity 初始容量
 * @throws IllegalArgumentException 当初始容量为小于0时抛出
 * is negative
 */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 这里当指定的初始容量为0时返回
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: " +
                initialCapacity);
    }
}

无参的构造器:

/**
 * 无参构造,返回DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

参数为Collection的构造器:

/**
 * 创建一个包含collection的ArrayList
 *
 * @param c 要放入ArrayList中的集合,其内元素将全部会添加到新建的ArrayList中
 * @throws NullPointerException 当c为空时
 */
public ArrayList(Collection<? extends E> c) {
    // 将集合转化成数组
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {         // 把数组的长度赋给ArrayList的size,并判断是否为空,当不等于空
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        // c.toArray 可能不会返回Object[] ,这是一个bug
        if (elementData.getClass() != Object[].class)
            // 若c.toArray()返回的数组不是Object[],则利用Arrays.copyOf();来构造一个大小为size的Object[]
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // 替换空的数组
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

ArrayList中的方法

1. trimToSize() 这个方法是用来调整缓冲区的大小的。
/**
 * 将数组的缓冲区调整到实际ArrayList存储元素的大小,即elementData = Arrays.copOf(elementData, size)
 * 该方法有用户手动调用目的是为了减少空间的浪费
 */
public void trimToSize() {
    modCount++; // 这个是记录ArrayList修改的次数
    // 当实际的大小 < 缓冲区大小时
    // 如调用默认的构造函数后,刚添加第一个元素,此时它的缓冲区的大小为10, 可以调用该函数调整
    if (size < elementData.length) {
        elementData = (size == 0)
                ? EMPTY_ELEMENTDATA
                : Arrays.copyOf(elementData, size);
    }
}
2. 上面我们看到了多次使用了Arrays.copy()方法。所以这里来看一下这个方法的源码。
// 复制指定的数组,截取或用 null 填充(如有必要),以使副本具有指定的长度。
// 对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。
// 对于在副本中有效而在原数组无效的所有索引,副本将包含 null。当且仅当指定长度大于原数组的长度时,
// 这些索引存在。所得数组和原数组属于完全相同的类。
@SuppressWarnings("unchecked")
public static <T> T[] copyOf(T[] original, int newLength) {
    // 这个方法调用了下面的这个方法
    return (T[]) copyOf(original, newLength, original.getClass());
}
// 这个方法和上面的方法不同的地方就是它有一个名为newType的参数,所以它最终返回的数组类型为newType。
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked") 
    // 判断newType是不是Object[]类型的
    T[] copy = ((Object)newType == (Object)Object[].class)
        // 如果是直接new
        ? (T[]) new Object[newLength]
        // 如果不是,通过调用这个方法获取,newType.getComponentType()获取数组组件类型的Class
        // 这个在往下就到native层了
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    // 下面的这个方法是一个native层的一个方法,在后面也用的比较多,这个是用来实现数组复制的
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

System.arraycopy()的原型如下:

public static native void arraycopy(Object src, int srcPos,
                                        Object dest, int destPos,
                                        int length);

它的四个参数的作用如下:

参数 作用
src 原数组,也就是需要复制的数组
srcPos 原数组的复制的起始位置
dest 目标数组
destPos 目标数组中的起始位置
length 复制的长度

下面的是Array.copyOf()和System.arraycopy()方法的简单使用:

import java.util.Arrays;
public class ArrayTest {
    public static void main(String[] args) {
        // Array.copyOf()的用法
        System.out.println("-------------Arrays.copyOf()的用法示例-------------");
        Object[] array = new Object[1];
        array[0] = "Hello";
        array = Arrays.copyOf(array, 3);
        for(Object o : array) {
            System.out.print((String)o);
        }
        System.out.println();
        array[1] = " ";
        array[2] = "World!";
        for(Object o : array) {
            System.out.print((String)o);
        }
        System.out.println("-------------System.arraycopy()的用法示例---------------");
        int[] arraySrc = new int[9];
        for (int i = 0; i < 9; i++) {
            arraySrc[i] = i;
        }
        int[] arrayDest = new int[10];
        System.arraycopy(arraySrc, 0, arrayDest, 0, 9);
        for (int i = 0; i < 10; i++) {
            System.out.println(arrayDest[i]);
        }
    }
}
3. add(E e)添加元素的方法
/**
 * Appends the specified element to the end of this list.
 * 将指定的元素添加到ArrayList最后的位置
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    // 确保ArrayList的容量
    // 保证要存多少个元素,就分配多少的空间
    ensureCapacityInternal(size + 1); // Increments modCount!!
    elementData[size++] = e;
    return true;
}

4. ensureCapacityInternal() 明确ArrayList的容量

/**
 * 私有的方法:明确ArrayList的容量
 * 用于内部优化,保证空间资源不浪费:尤其在add()方法中
 *
 * @param minCapacity 指定最小容量
 */
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

这个方法调用了calculateCapacity()方法,这个方法时一个私有的静态方法。

4. calculateCapacity() 明确ArrayList的容量
/**
 * 私有的静态方法:明确ArrayList的容量
 *
 * @param elementData
 * @param minCapacity
 * @return 返回的是经过计算后的ArrayList的容量
 */
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 如果elementData为空的话,返回默认用量和minCapacity中的较大者
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 比较指定容量和默认容量那个大,返回较大的
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    // 如果不为空,返回minCapacity
    return minCapacity;
}
5. ensureExplicitCapacity()
/**
 * 私有的方法:明确ArrayList的容量
 * 用户内部优化,保证空间资源不浪费:尤其在add()方法中
 * 防止溢出
 * @param minCapacity
 */
private void ensureExplicitCapacity(int minCapacity) {
    // 将修改的统计次数加1
    modCount++;
    // overflow-conscious code
    // 防止溢出,确保最小容量 > 数组缓冲区的容量
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
6. grow(int minCapacity) 这个方法时用来扩容的

这个是ArrayList中一个比较重要的内容:数组扩容。

/**
 * 私有方法:扩容,以确保能存储minCapacity个元素
 * - 扩容计算:newCapacity = oldCapacity + (oldCapacity >> 1); 扩容是当前容量的1.5倍
 * 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;
    // 扩容的策略是newCapacity =  oldCapacity oldCapacity / 2
    int newCapacity = oldCapacity + (oldCapacity >> 1);
     // 如果使用这种策咯扩容之后比最小容量小,那么让newCapacity = minCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 也就是newCapacity > MAX_ARRAY_SIZE 也就是新的容量比最大的容量还要大
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);    // 其实这个方法当minCapacity >MAX_ARRAY_SIZE 时,返回Integer.MAX_VALUE
    // minCapacity is usually close to size, so this is a win: 这里使用了Arrays.copyOf()这个方法进行扩容
    elementData = Arrays.copyOf(elementData, newCapacity);
}

下面的就是hugeCapacity(int minCapacity)的源码。

/**
 * 私有方法:最大容量分配,最大分配Integer.MAX_VALUE
 *
 * @param minCapacity
 * @return
 */
private static int hugeCapacity(int minCapacity) {
    // 小于0,抛出异常 OutOfMemoryError
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    // 指定最小的容量大于最大能分配的容量,返回Integer.MAX_VALUE,否则返回能分配的最大值
    return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
}
6. toArray() 这个方法就是将ArrayList变成一个Object数组,对返回的数组操作不会影响到原ArrayList.
/**
 * 将该数组转化成Object数组
 * - 包含ArrayList的所有元素
 * - 对返回的数组操作,不会影响ArrayList
 * - 元素的存储与ArrayList一致
 * @return an array containing all of the elements in this list in 返回一个包含ArrayList元素的数组
 * proper sequence
 */
public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}

这里可以看到这个方法就是用了Arrays.copy()方法。

7. toArray(T[] a)
/**
 * 返回ArrayList元素组成的数组
 */
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
    // 若a.length < size时新建一个T[]数组
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    // 若数组a的大小 >= ArrayList中元素的个数,则将ArrayList中的元素全部拷贝到a数组中
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

这个方法可能看一些JDK的解释也看的稀里糊涂的(我就是这样)。
通过看它的源码就会发现,它有两种情况:
1. 数组的长度小于ArrayList的size时(a.length < size),调用之前介绍的Arrays.copyOf()方法复制elementData中的元素(也就是复制ArrayList中的元素)。
2. 数组的长度大于等于ArrayList的size,将elementData中的元素从0开始赋值给a。然后将a[size]赋值为null。
下面的是它的一个测试示例:

import java.util.*;
public class ArrayListToArray {
    public static void main(String[] args) {
        System.out.println("---------第一种情况-----------");
        String[] array1 = {"Hello", " ", "World!"};
        ArrayList<String> list1 = new ArrayList();
        list1.add("my");
        list1.add("name");
        list1.add("is");
        list1.add("emmmmm");
        String[] arrayReturn1 = list1.toArray(array1);
        System.out.println("array数组:");
        for (String str : array1) {
            System.out.println(str);
        }
        System.out.println("list数组:");
        for (String str : list1) {
            System.out.println(str);
        }
        System.out.println("arrayReturn数组:");
        for (String str : arrayReturn1) {
            System.out.println(str);
        }
        System.out.println("---------第二种情况-----------");
        String[] array2 = {"Hello", " ", "World!", "Hello", " ", "ArrayList", "!"};
        ArrayList<String> list2 = new ArrayList();
        list2.add("my");
        list2.add("name");
        list2.add("is");
        list2.add("emmmmm");
        String[] arrayReturn2 = list2.toArray(array2);
        System.out.println("array数组:");
        for (String str : array2) {
            System.out.println(str);
        }
        System.out.println("list数组:");
        for (String str : list2) {
            System.out.println(str);
        }
        System.out.println("arrayReturn数组:");
        for (String str : arrayReturn2) {
            System.out.println(str);
        }
    }
}
8. add(int index, E element)
/**
 * 在这个ArrayList中指定位置插入指定的元素
 * - 在指定位置之前插入新的元素,原先的index位置往后移动一位
 * @param index index at which the specified element is to be inserted
 * @param element element to be inserted
 * @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++;
}

这里有一个越界检查的方法,这个方法很简单,是一个为add提供的范围检查的方法,若果越界则抛出IndexOutOfBoundsException

/**
 * 范围检查用于add和addAll
 * A version of rangeCheck used by add and addAll.
 */
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;
}
9. remove(int index)
/**
 * 删除指定位置的元素
 * index后的索引元素一次左移一位
 * @param index the index of the element to be removed index指定位置
 * @return the element that was removed from the list 被移出的元素
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E remove(int index) {
    // 检查index越界
    rangeCheck(index);
    // 修改次数加一
    modCount++;
    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;

这个方法的源码还是很好懂的,ArrayList中有关数组复制的几乎都是用的System.arraycopy()这个方法。

10. remove(Object o) 删除指定的元素
/**
 * 移出list中指定的第一个元素(符合条件索引的最低值)
 * 如果ArrayList中不包含这个元素,那么ArrayList将不会被改变
 * @param o element to be removed from this list, if present
 * @return <tt>true</tt> if this list contained the specified element
 */
public boolean remove(Object o) {
    // 如过o = null
    if (o == null) { // 遍历找到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;
}

因为ArrayList集合中的元素可以是null,所以如果传入的是null,那么首先它会去寻找第一个为null的元素。
如果不是的话,那么他会去使用元素的equals方法去判断是否相等。这里也就是我们有时候要重写类的equals方法的原因了。
上面的方法中调用了一个fastRemove(index),这个方法是ArrayList的一个私有的方法。
它的源码如下:

/*
 * 快速删除第index个元素
 * Private remove method that skips bounds checking and does not
 * return the value removed.
 */
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 将最后一个元素清除
}
11. clear()清空ArrayList

这个方法的作用是将ArrayList的元素全部清空。

/**
 * 删除该list中的所有元素
 * - 他会将数组缓冲区所有元素置为null
 * - 清除之后,打印list会出现[], 而不是null, 这是因为toSting()和迭代器处理了
 * Removes all of the elements from this list. The list will
 * be empty after this call returns.
 */
public void clear() {
    modCount++;
    // clear to let GC do its work 这里把他们去不置为空,让GC工作
    for (int i = 0; i < size; i++)
        elementData[i] = null;
    size = 0;
}
12. addAll()

addAll(Collection

/**
 * 将一个集合中的所有元素追加到list的末尾
 * - ArrayList是线程不安全的,当一个线程正在将c中的元素添加到list中,
 * 但同时另一个线程在更改c中的元素,可能会产生问题
 * @param c collection containing elements to be added to this list 要追加的集合
 * @return <tt>true</tt> if this list changed as a result of the call
 * @throws NullPointerException if the specified collection is null
 */
public boolean addAll(Collection<? extends E> c) {
    // 将c转化为一个object数组
    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;
}
13. 序列化的方法

上面提到了elementData和size不参与序列化,但是平常使用的时候ArrayList是可以正常的序列化的,因为它有自己的序列化和反序列化的方法。
序列化方法writeObject()

/**
 * 私有方法
 * 将ArrayList实例序列化
 */
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);
    // Write out all elements in the proper order.
    // 以合适的顺序写入所有的元素
    for (int i = 0; i < size; i++) {
        s.writeObject(elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

这里就体现出modCount的作用了,如果正在序列化的时候ArrayList被修改了,那么modCount的值肯定会变大,如果序列化结束后modCount的大小和之前记录的大小不一样大,那么肯定ArrayList在序列化的过程中被修改了,那么就会抛出ConcurrentModificationException异常。
readObject()

/**
 * 私有的方法
 * 以反序列化中重构ArrayList实例
 * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
 * deserialize it).
 */
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(); // ignored
    if (size > 0) {
        // be like clone(), allocate array based upon size not capacity
        int capacity = calculateCapacity(elementData, size);
        SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, 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();
        }
    }
}

readObject()方法将那么没有参与序列化的属性读取出来。

14. ArrayList中还有一个clone()方法
/**
 * 实现Cloneable接口,深度复制
 * Returns a shallow copy of this <tt>ArrayList</tt> instance. (Th
 * elements themselves are not copied.)
 *
 * @return a clone of this <tt>ArrayList</tt> instance
 */
public Object clone() {
    try {
        // Object的克隆会复制本对象的其内的基本类型和String类型,但不会复制对象
        ArrayList<?> v = (ArrayList<?>) super.clone();
        // 将elemetData进行复制
        v.elementData = Arrays.copyOf(elementData, size);
        // 因为复制出来的是新的,没有被修改过,所以modCount初始化为0
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

剩余的方法还是比较多的但是都比较好理解,与数组中元素移动相关的都使用到了System.arraycopy()这个方法。所以在这里就不多讲了。
下面将一些比较重要的方法

猜你喜欢

转载自blog.csdn.net/starexplode/article/details/80469079