基本java开发面试都会问到关于collection(集合)框架的问题,collection是一个接口,常用的实现类有 ArrayList,HashSet, LinkedHashSet, LinkedList 等等,这里主要学习一下ArrayList。ArrayList平时经常用来返回多个值的对象,比如获取多数据等场景。ArrayList具体是怎么实现的呢?或者说当我们添加一个元素或者对象时底层怎么处理,我们来看一下源码:
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to
* DEFAULT_CAPACITY when the first element is added.
*/
private transient Object[] elementData;
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
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);
}
上面是ArrayList的构造函数源码,我们常用的ArrayList()这个方法来创建一个list,英文的解释是构造一个空的且容量为10的list;也就是说初始化list容量为10,因为list采用的是动态调整内存的方式当数据量超过10时他会自动的扩充容量(扩充函数在ArrayList源码中也可以查看)。在第一段源码中我们可以看到ArrayList的数据结构是一个数组,这个很重要,说明底层的操作都是对数组进行的操作。看一下ArrayList的add函数
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ensureCapacityInternal 动态调整容量大小的方法
我们可以看到当我调用add方法时直接在ArrayList最后面插入一个元素,也就说ArrayList集合其实是一个动态可变的数组。
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方法多了个参数index,这参数表示你可以在指定的位置进行进行擦入。具体执行:判断输入位置
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
是不是大于size(默认是10)是则直接跑出异常,否则动态调整大小,并执行:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
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:
elementData = Arrays.copyOf(elementData, newCapacity);
}
将elementData指向copy后的数组。到此ArrayList的动态扩容核心操作完成。接下来是数组位置的移动:
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
参数解释:第一个elementData源数组,index数组需要复制的开始位置,第二个elementData是目标数组,第三个参数是目标数组的开始下标,最后一个参数表示需要复制的长度。代码如下:
int[] a = {1,4,6,7,8,9}; int[] b = {2,3,5}; //复制数组 System.arraycopy(a,2,b,0,3); System.out.println(Arrays.toString(b));
输出:
[6, 7, 8]
从2的位置开始复制,长度为3个元素,在b的0的位置开始插入,这样就完成数组的移动。这里b数组开始下标必须在b数组中初始化或者设置长度否则会抛出异常ArrayIndexOutOfBoundsException,复制的长度也必须与小于等于b数组长度。到此ArrayList add结束。