没事来聊聊ArrayList源码

首先让我们看下ArrayList的类图吧。
没事来聊聊ArrayList源码_第1张图片
从类图中可以看到ArrayList类继承AbstractList类并实现了List接口,同时实现Cloneable和Serializable接口使得ArrayLis可克隆并且可序列化。

成员变量

transient Object[] elementData;

elementData用于存储我们添加到ArrayList中的元素。从这里可以确定ArrayList确实是名副其实的由数组实现。

private static final int DEFAULT_CAPACITY = 10;

数组默认大小。如果调用无参构造方法的话就会将elementData的长度初始化为10。

private static final Object[] EMPTY_ELEMENTDATA = {};

空对象。

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

空对象。如果调用无参构造方法则会将elementData初始化为该值。

private int size;

现在保存的元素数量。

protected transient int modCount;

用于记录list被更改的次数。由于ArrayList是非线程安全的,所以此值也用于检测是否出现了多线程问题。

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

可保存的最大元素数量。

构造方法

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

无参构造方法,将elementData初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA

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

定长构造方法,将elementData初始化为EMPTY_ELEMENTDATA并进行输入检查。

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();//将elementData指向collection转换后数组
        if ((size = elementData.length) != 0) {
        	//如果数组不为空,执行copy方法将collection中元素复制到elementData 
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //Collection为空直接指向空数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
}

有参构造方法,根据给定的Collection初始化为ArrayList。

add方法

public boolean add(E e) {
		//进行elementData大小检查,确认是否需要进行扩容
       	ensureCapacityInternal(size + 1); 
       	//扩容检查完成后将元素存储到对应位置。
        elementData[size++] = e;
        return true;
}

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        	//如果elementData为空数组,则最小容量为默认容量10
            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;
        //默认新数组长度为旧数组的1.5倍 右移,相当于原有容量的1/2
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
        //进行大小检查,如果新长度仍然小于所需最小长度,则直接使用最小长度作为初始化大小
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        //进行最大值检查,仅在未超过最大长度时进行扩容。
            newCapacity = hugeCapacity(minCapacity);
        // 将elementData指向复制后的新数组
        elementData = Arrays.copyOf(elementData, newCapacity);
}
    
//最大值计算,可存储最大值为Integer.MAX_VALUE,即2147483647
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
        	//说明需要存储的数量已经超过Integer.MAX_VALUE,直接抛异常
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

在这里总结下add方法的流程:

  1. 确定是否初次添加元素,是则将最小容量设置为10。
  2. 记录此次更改,将modCount自增并进行扩容检查。
  3. 如果所需最小容量大于elementData则需要进行扩容,默认将容量扩充为原来的1.5倍。
  4. 扩容检查完成后将需要添加的元素保存到size++位置。

从这里可以确认的是,如果在初始化时不给出确定的大小的话,在存储元素不断增长时是会进行多次扩容操作的,这个操作相比较而言还是很耗费时间的。所以还是建议在初始化时给出一个默认容量。

get方法

相比较来说,get方法就比较简答了。先检查输入index是否越界,不越界则直接返回对应元素。

public E get(int index) {
	//边界检查
	rangeCheck(index);
	//根据索引返回所需元素
	return elementData(index);
}
private void rangeCheck(int index) {
	if (index >= size)
		throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

你可能感兴趣的:(Java,#,JVM源码笔记)