JAVA集合之ArrayList源码解析(JDK8)

文章目录

    • 前言
    • 源码解析
        • 一、ArrayList类
        • 二、ArrayList属性
        • 三、ArrayList构造器
          • 无参数构造
          • 参数为初始化元素数组大小构造
          • 参数为集合时构造
        • 四、ArrayList扩容详解
        • 五、ArrayList主要方法
        • 六、ArrayList优缺点
    • 总结

前言

我们在存储多个元素时,如果元素个数固定,那么可以使用数组即可,但是在需要存储的元素个数不确定时,这时候数组可以出现数据下标越界的情况,数组对于动态元素添加显得无能为力,而ArrayList可以根据实际元素个数的变化而动态扩容,弥补了数组的局限性

源码解析

一、ArrayList类

 public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList具备List的特性,同时支持随机访问,原生支持序列化

二、ArrayList属性

    //默认容量
    private static final int DEFAULT_CAPACITY = 10;
    
    //空数组,当初始化指定容量为0或者指定初始化元素个数为0时使用
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    //空数组 区别于上面空数组,仅在无参数构造器时使用,表示默认值
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    //用于存储数据数组
    transient Object[] elementData; // non-private to simplify nested class access
    
    //该ArrayList中的元素个数   
    private int size;

三、ArrayList构造器

ArrayList具有三个构造方法,对应不同初始化

无参数构造
    public ArrayList() {
    	//直接将默认空数组赋值给存储元素的数组
    	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
参数为初始化元素数组大小构造
    public ArrayList(int initialCapacity) {
    	if (initialCapacity > 0) {
    		//当指定参数大于0,则直接new对应大小的元素素组
    		this.elementData = new Object[initialCapacity];
    	} else if (initialCapacity == 0) {
    		//当指定参数等于0,赋值为空数组
    		this.elementData = EMPTY_ELEMENTDATA;
    	} else {
    		//如果参数小于0,则抛出异常
    		throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    	}
    }
参数为集合时构造
    public ArrayList(Collection<? extends E> c) {
    	//首先将集合转为数组
    	elementData = c.toArray();
    	if ((size = elementData.length) != 0) {
    		//如果集合元素不为空并且不为Object数组,则将数组修正为Object数组
    		if (elementData.getClass() != Object[].class)
    			elementData = Arrays.copyOf(elementData, size, Object[].class);
    	} else {
    		//如果集合元素为空,则直接初始化空数组
    		this.elementData = EMPTY_ELEMENTDATA;
    	}
    }

四、ArrayList扩容详解

ArrayList最大的特点就是可以动态扩容,下面我们分析扩容过程

    //确认容量是否需要扩容
    private void ensureCapacityInternal(int minCapacity) {
    	
    	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    		//如果为默认初始化,则将最小所需容量设置为默认大小10和传入参数(希望最小容量)的最大值
    		//在第一次添加元素时,数组容量默认值为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.5倍)
    	int newCapacity = oldCapacity + (oldCapacity >> 1);
    	if (newCapacity - minCapacity < 0)
    		//如果扩容后还是小于最小所需容量,则将最小所需容量设置为新容量值
    		newCapacity = minCapacity;
    	//这里的MAX_ARRAY_SIZE为 Integer.MAX_VALUE - 8,如果分配更大的值,可能会造成OutOfMemoryError,但是ArrayList支持将容量扩大到Integer.MAX_VALUE
    	if (newCapacity - MAX_ARRAY_SIZE > 0) 
    		//如果大于ArrayList所希望的最大值,继续扩容
    		newCapacity = hugeCapacity(minCapacity);
    	//将原存储元素数组中的所有元素拷贝之新数组
    	elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    private static int hugeCapacity(int minCapacity) {
    	//这里minCapacity小于零是不可能的,只有在该值已经溢出Integer的值才会出现小于零的情况,抛出异常
    	if (minCapacity < 0) 
    		throw new OutOfMemoryError();
    	//分配容量最大值为Integer.MAX_VALUE
    	return (minCapacity > MAX_ARRAY_SIZE) ?
    		Integer.MAX_VALUE :
    		MAX_ARRAY_SIZE;
    }

扩容过程主要分为3步:

  1. 确定需要的容量,如果大于当前容量,则触发扩容;
  2. 基于原容量的1.5倍计算新容量值,如果新容量值大于所需要的容量值则使用新容量值否则使用所需要的容量值
  3. 创建新容量值的数组,将原数组中元素拷贝至新数组

图解扩容过程
JAVA集合之ArrayList源码解析(JDK8)_第1张图片

ArrayList每次扩容就是新建一个存储元素数组,数组大小为原数组1.5倍,然后将原数组元素拷贝至新数组

五、ArrayList主要方法

顺序添加元素 add(E e) 方法

public boolean add(E e) {
	//确定容量
	ensureCapacityInternal(size + 1); 
	//将元素添加至现有元素的末尾
	elementData[size++] = e;
	return true;
}

只要清楚ArrayList的扩容过程,添加元素的过程就很简单了,只需要将元素添加到现有元素的末尾

指定位置添加元素 add(E e)

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

删除元素 remove() 方法

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);
	elementData[--size] = null; // clear to let GC do its work
	return oldValue;
}

六、ArrayList优缺点

优点:ArrayList基于数组实现,在随机访问上又很大的优势,可以通过数组的下标直接访问对应的数据,对于修改数据同样也很高效,可以通过指定位置取出元素进行修改或替换,动态扩容的机制很好适应使用环境的变化
缺点:ArrayList对于删除和随机插入的性能并是很好,只要不是添加或者删除末尾元素就会涉及到位置整理的过程,如果对于需要存入大量数据,并且不指定初始化容量,会导致频繁的扩容,将会影响整体业务的性能

总结

ArrayList对于顺序添加和删除的场景是非常适合的,具备数组的高效的同时也兼备了动态的特性,由于扩容过程中的拷贝会损耗性能,在使用时可以估算需要的大小,在初始化时ArrayList使用带容量参数的构造器指定初始化容量,尽量避免触发扩容,提升性能。

(转载请注明出处)

你可能感兴趣的:(JAVA,JDK源码)