ArrayList是Java集合框架中非常常用的一种数据结构。继承自AbstractList,实现了List接口。底层基于数组来实现动态容量大小的控制,允许null值的存在。同时还实现了RandomAccess、Cloneable、Serializable接口,支持快速访问、复制、序列化操作。
了解数组数组简单来说就是将所有的数据排成一排存放在系统分配的一个内存块上,通过使用特定元素的索引作为数组的下标,可以在常数时间内访问数组元素的这么一个结构;
数组优缺点 优点简单方便已使用
访问元素快
缺点大小固定:数组的大小是静态的,在使用前必须确定好数组的大小
分配一个连续空间块:数组初始分配空间时,有时候无法分配能存储整个数组的内存空间(当数组规模太大时);
基于位置的插入操作实现复杂:如果要在数组中的给定位置插入元素,那么可能就会需要移动存储在数组中的其他元素,这样才能腾出指定的位置来放插入的新元素;而如果在数组的开始位置插入元素,那么这样的移动操作开销就会很大。
ArrayList解析我们提到数组的特点是大小固定,ArrayList的底层是基于数组来实现容量的大小动态变化的,那我们一起来结合源码看看,是如何实现这一功能的。
我们找到java.util.ArrayList包查看代码。并通过注释的方式,一起来揭开面纱。
1、成员变量 // 默认的容量大小 private static final int DEFAULT_CAPACITY = 10; // 空数组对象Object private static final Object[] EMPTY_ELEMENTDATA = {}; // 有一个空数据对象Object private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 默认修饰且不参与序列化的 数组对象,也是实际存储数据的地方 transient Object[] elementData; // non-private to simplify nested class access // 实际大小容量 private int size;我们发现有两个一样的空数组对象,为什么要用两个呢?源代码中也进行来解释 We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.
也就是说,这是一个共享的空数组实例,通过与默认的空数组区分开,好处是,添加元素时知道该 elementData 从空的构造函数还是有参构造函数被初始化的。以便确认如何扩容。
在AbstractList父类中还有一个变量
protected transient int modCount = 0;
用来记录对List的操作次数。作用在使用Iterator时,防止在迭代过程中集合被修改。
2、构造函数 无参数构造 /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }默认的无参数构造中直接给elementData赋值来一个空的数据。但我们看到注释上说初始容量为10的数组,这好像不太对啊。其实这只是一个延后的操作,当第一次添加数据进去时,容量会扩容到10,好处是避免无用的ArrayList的出现。具体的实现我们接着往后看。
指定初始容量构造 public ArrayList(int initialCapacity) { // 指定的容量大于0,直接new一个指定容量大小的数组 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; // 指定容量等于0。那就赋值空数组。 } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { // 无效容量大小 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }这个还是比较清晰的,根据指定容量初始化一个数组。加入了容量大小的判断操作。
指定Collection集合构造 public ArrayList(Collection<? extends E> c) { // 首先转成数组 Object[] a = c.toArray(); // 有效大小的数组哈 if ((size = a.length) != 0) { // 这里做了优化,如果也是一个ArrayList集合直接赋值即可 if (c.getClass() == ArrayList.class) { elementData = a; } else { // 其他的类型就做拷贝啦 elementData = Arrays.copyOf(a, size, Object[].class); } } else { // replace with empty array. elementData = EMPTY_ELEMENTDATA; } }简单点说其实就是把集合转成数组,然后赋值给elementData。可能你看到的版本不一样,主要是在
c.getClass() == ArrayList.class做了优化。如果也是ArrayList的集合,那就不用做数组拷贝了,这个还是比较耗性能的。
主要操作函数解析下面将主要的增删改操作进行分析
添加元素操作单元素添加
public boolean add(E e) { // 我们需要添加 一个元素,则需要判断+1后的容量是否需要扩容了,同时记录modCount ensureCapacityInternal(size + 1); // Increments modCount!! // 接在index后添加元素,并且更新当前集合大小size // 可以理解成 index =size+1;elementData[index];size++ elementData[size++] = e; return true; } // 提取方法哈,比较计算,确定容量大小 //private static int calculateCapacity(Object[] elementData, int minCapacity) { // if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // return Math.max(DEFAULT_CAPACITY, minCapacity); // } // return minCapacity; //} // 做了简化,与calculateCapacity 一致 private void ensureCapacityInternal(int minCapacity) { //ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); // 提取calculateCapacity方便可以做一个简化,可能你的版本就是这样 // 判断是否是空数组,如果是空数组,那么minCapacity = 10 // 这样就解答了我们前面提到的问题,在添加第一个元素时才将容量设置成10 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity) } // 记录操作次数,然后判断是否需要扩容 private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code // 当添加一个元素后的容量大于当前元素个数,则需要扩容了 if (minCapacity - elementData.length > 0) grow(minCapacity); } // 扩容方法。minCapacity 添加一个元素后的容量 private void grow(int minCapacity) { // overflow-conscious code // 先记录当前容量,也就是元素的个数 int oldCapacity = elementData.length; // 不管,先扩容1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); // 如果扩容后的大小小于添加元素后的容量,则没必要了,直接用添加元素后的容量了 // 这里可以看到哈,如果是空构造,添加参数时,newCapacity是等于0的,然后再赋值了10。 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: // 数组拷贝 底层还是System.arraycopy elementData = Arrays.copyOf(elementData, newCapacity); }