源码:
/** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. */ private transient Object[] elementData; /** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size; 关注点结论是否允许空√是否允许重复数据√是否有序√是否线程安全×构造函数
ArrayList提供了三个构造函数:
ArrayList():默认构造函数,提供初始容量为10的空列表。ArrayList(int initialCapacity):构造一个具有指定初始容量的空列表。ArrayList(Collection<? extends E> c):构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。 /** * 构造一个初始容量为 10 的空列表 */ public ArrayList() { this(10); } /** * 构造一个具有指定初始容量的空列表。 */ public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); this.elementData = new Object[initialCapacity]; } /** * 构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。 */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); }添加元素
public boolean add(E e) { ensureCapacity(size + 1); // Increments modCount!! elementData[size++] = e; return true; }扩容
构造ArrayList的时候,默认的底层数组大小是10;那么底层数组的大小不够了怎么办?答案就是扩容,这也就是为什么一直说ArrayList的底层是基于动态数组实现的原因,动态数组的意思就是指底层的数组大小并不是固定的,而是根据添加的元素大小进行一个判断,不够的话就动态扩容,扩容的代码就在ensureCapacity里面:
public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3)/2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } }扩容的时候把元素组大小先乘以3,再除以2,最后加1。这是为了协以下问题: 1、如果一次性扩容扩得太大,必然造成内存空间的浪费 2、如果一次性扩容扩得不够,那么下一次扩容的操作必然比较快地会到来,这会降低程序运行效率,要知道扩容还是比价耗费性能的一个操作
所以扩容扩多少,是JDK开发人员权衡后提供出来的一个比较合理的数值。最后调用到的是Arrays的copyOf方法,将元素组里面的内容复制到新的数组里面去:
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }插入元素
插入操作调用的也是add方法,add(index,E e);
// 将指定的元素插入此列表中的指定位置。 // 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。 public void add(int index, E element) { if (index > size || index < 0) throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size); // 如果数组长度不足,将进行扩容。 ensureCapacity(size+1); // Increments modCount!! // 将 elementData中从Index位置开始、长度为size-index的元素, // 拷贝到从下标为index+1位置开始的新的elementData数组中。 // 即将当前位于该位置的元素以及所有后续元素右移一个位置。 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }在这个方法中最根本的方法就是System.arraycopy()方法,该方法的根本目的就是将index位置空出来以供新数据插入,这里需要进行数组数据的右移,这是非常麻烦和耗时的,所以如果指定的数据集合需要进行大量插入(中间插入)操作,推荐使用LinkedList。
删除元素
ArrayList支持两种删除方式:
1、按照下标删除
2、按照元素删除,这会删除ArrayList中与指定要删除的元素匹配的第一个元素
int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work反转一个List或ArrayList
import java.util.Collections; Collections.reverse(lists);ArrayList的优缺点
优点:
ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找也就是get的时候非常快。ArrayList在顺序添加一个元素的时候非常方便,只是往数组里面添加了一个元素而已。缺点:
删除元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能。插入元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能。适用情况:
顺序添加、随机访问